aboutsummaryrefslogtreecommitdiff
path: root/service-builtin/src
diff options
context:
space:
mode:
authorKeun young Park <keunyoung@google.com>2021-07-29 14:44:30 -0700
committerKeun young Park <keunyoung@google.com>2021-08-03 17:09:36 -0700
commit51773dfd7d367534604406c52c52de95a99a35ca (patch)
tree9ebac3b7138d67f45007d56739eed530a07e6e59 /service-builtin/src
parent89026789d664857c14726a8be4ee0d6bd52580c1 (diff)
downloadCar-51773dfd7d367534604406c52c52de95a99a35ca.tar.gz
Split car service into two
- directory service-builtin hosts com.android.car which is builtin and is not in module * Still module name is CarService * hosts all permissions, all external facing Services and Activity - service hosts com.android.car.updatable which is added to mainline module * Module name is CarServiceUpdatable * hosts actual service codes to run carservice * code namespace is still com.android.car - disable some tests for now: * ScriptExecutorTest: It should be done differently after splitting the scriptexecutor. Bug: 195013206 Test: atest CarServiceTest CarServieUnitTest Change-Id: I25e6fcfab38c48cca25fe31632f87fe9568dbfb4
Diffstat (limited to 'service-builtin/src')
-rw-r--r--service-builtin/src/com/android/car/CarService.java33
-rw-r--r--service-builtin/src/com/android/car/PerUserCarService.java82
-rw-r--r--service-builtin/src/com/android/car/ServiceProxy.java134
-rw-r--r--service-builtin/src/com/android/car/UpdatablePackageContext.java77
-rw-r--r--service-builtin/src/com/android/car/UpdatablePackageDependency.java52
-rw-r--r--service-builtin/src/com/android/car/admin/FactoryResetActivity.java190
-rw-r--r--service-builtin/src/com/android/car/admin/NewUserDisclaimerActivity.java115
-rw-r--r--service-builtin/src/com/android/car/admin/NotificationHelper.java91
-rw-r--r--service-builtin/src/com/android/car/admin/PerUserCarDevicePolicyService.java189
-rw-r--r--service-builtin/src/com/android/car/am/ContinuousBlankActivity.java42
-rw-r--r--service-builtin/src/com/android/car/pm/ActivityBlockingActivity.java432
-rw-r--r--service-builtin/src/com/android/car/pm/CarSafetyAccessibilityService.java67
-rw-r--r--service-builtin/src/com/android/car/pm/blurredbackground/BlurTextureProgram.java337
-rw-r--r--service-builtin/src/com/android/car/pm/blurredbackground/BlurredSurfaceRenderer.java196
-rw-r--r--service-builtin/src/com/android/car/pm/blurredbackground/GLHelper.java195
15 files changed, 2232 insertions, 0 deletions
diff --git a/service-builtin/src/com/android/car/CarService.java b/service-builtin/src/com/android/car/CarService.java
new file mode 100644
index 0000000000..a848f6b0d6
--- /dev/null
+++ b/service-builtin/src/com/android/car/CarService.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import android.content.Intent;
+
+/** Proxy service for CarServciceImpl */
+public class CarService extends ServiceProxy {
+
+ public CarService() {
+ super(UpdatablePackageDependency.CAR_SERVICE_IMPL_CLASS);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // keep it alive.
+ return START_STICKY;
+ }
+}
diff --git a/service-builtin/src/com/android/car/PerUserCarService.java b/service-builtin/src/com/android/car/PerUserCarService.java
new file mode 100644
index 0000000000..6586065e86
--- /dev/null
+++ b/service-builtin/src/com/android/car/PerUserCarService.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.car.builtin.util.Slogf;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.car.admin.PerUserCarDevicePolicyService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/** Proxy service for PerUserCarServiceImpl */
+public class PerUserCarService extends ServiceProxy {
+ private static final boolean DBG = false;
+ private static final String TAG = PerUserCarService.class.getSimpleName();
+
+ private @Nullable PerUserCarDevicePolicyService mPerUserCarDevicePolicyService;
+
+ public PerUserCarService() {
+ super(UpdatablePackageDependency.PER_USER_CAR_SERVICE_IMPL_CLASS);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate(); // necessary for impl side execution
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
+ mPerUserCarDevicePolicyService = PerUserCarDevicePolicyService.getInstance(this);
+ mPerUserCarDevicePolicyService.onCreate();
+ } else if (DBG) {
+ Slogf.d(TAG, "Not setting PerUserCarDevicePolicyService because device doesn't have %s",
+ PackageManager.FEATURE_DEVICE_ADMIN);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mPerUserCarDevicePolicyService != null) {
+ mPerUserCarDevicePolicyService.onDestroy();
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(fd, writer, args);
+ try (IndentingPrintWriter pw = new IndentingPrintWriter(writer)) {
+ if (mPerUserCarDevicePolicyService != null) {
+ pw.println("PerUserCarDevicePolicyService");
+ pw.increaseIndent();
+ mPerUserCarDevicePolicyService.dump(pw);
+ pw.decreaseIndent();
+ } else {
+ pw.println("PerUserCarDevicePolicyService not needed");
+ }
+ pw.println();
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // keep it alive.
+ return START_STICKY;
+ }
+}
diff --git a/service-builtin/src/com/android/car/ServiceProxy.java b/service-builtin/src/com/android/car/ServiceProxy.java
new file mode 100644
index 0000000000..549e052277
--- /dev/null
+++ b/service-builtin/src/com/android/car/ServiceProxy.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import static com.android.car.UpdatablePackageDependency.PROXIED_SERVICE_DO_ATTACH_BASE_CONTEXT;
+import static com.android.car.UpdatablePackageDependency.PROXIED_SERVICE_DO_DUMP;
+import static com.android.car.UpdatablePackageDependency.PROXIED_SERVICE_SET_BUILTIN_PACKAGE_CONTEXT;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.car.builtin.util.Slog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Base class to wrap Service lifecycle with real Service code loaded from updatable car service
+ * package. Public Service defined inside builtin should inherit this to provide automatic
+ * wrapping.
+ */
+public class ServiceProxy extends Service {
+ private static final String TAG = "CAR.ServiceProxy";
+
+ private final String mRealServiceClassName;
+
+ private UpdatablePackageContext mUpdatablePackageContext;
+ private Class mRealServiceClass;
+ private Service mRealService;
+
+ public ServiceProxy(String realServiceClassName) {
+ mRealServiceClassName = realServiceClassName;
+ }
+
+ @Override
+ public void onCreate() {
+ init();
+ mRealService.onCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ mRealService.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mRealService.onBind(intent);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ return mRealService.onUnbind(intent);
+ }
+ @Override
+ public void onRebind(Intent intent) {
+ mRealService.onRebind(intent);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return mRealService.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ executeAMethod(PROXIED_SERVICE_DO_DUMP,
+ new Class[]{FileDescriptor.class, PrintWriter.class, args.getClass()},
+ new Object[]{fd, writer, args}, true);
+ }
+
+ private void init() {
+ mUpdatablePackageContext = UpdatablePackageContext.create(this);
+ try {
+ mRealServiceClass = mUpdatablePackageContext.getClassLoader().loadClass(
+ mRealServiceClassName);
+ // Use default constructor always
+ Constructor constructor = mRealServiceClass.getConstructor();
+ mRealService = (Service) constructor.newInstance();
+ executeAMethod(PROXIED_SERVICE_DO_ATTACH_BASE_CONTEXT, new Class[]{Context.class},
+ new Object[]{mUpdatablePackageContext}, false);
+ executeAMethod(PROXIED_SERVICE_SET_BUILTIN_PACKAGE_CONTEXT, new Class[]{Context.class},
+ new Object[]{this}, false);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot load class:" + mRealServiceClassName, e);
+ }
+ }
+
+ /** Reflecion helper */
+ @Nullable
+ public Object executeAMethod(String methodName, Class[] argClasses, Object[] args,
+ boolean ignoreFailure) {
+ try {
+ Method m = mRealServiceClass.getMethod(methodName, argClasses);
+ return m.invoke(mRealService, args);
+ } catch (Exception e) {
+ String msg = "cannot load method:" + methodName + " for:" + mRealServiceClassName;
+ if (ignoreFailure) {
+ Slog.w(TAG, msg, e);
+ return null;
+ } else {
+ throw new RuntimeException(msg, e);
+ }
+ }
+ }
+
+ /** Check {@link Service#attachBaseContext(Context)}. */
+ public void doAttachBaseContext(Context newBase) {
+ attachBaseContext(newBase);
+ }
+
+ /** Check {@link Service#dump(FileDescriptor, PrintWriter, String[])}. */
+ public void doDump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ dump(fd, writer, args);
+ }
+}
diff --git a/service-builtin/src/com/android/car/UpdatablePackageContext.java b/service-builtin/src/com/android/car/UpdatablePackageContext.java
new file mode 100644
index 0000000000..1f87891d9c
--- /dev/null
+++ b/service-builtin/src/com/android/car/UpdatablePackageContext.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+
+/** Context for updatable package */
+public class UpdatablePackageContext extends ContextWrapper {
+
+ public static final String UPDATABLE_CAR_SERVICE_PACKAGE_NAME = "com.android.car.updatable";
+
+ // This is the package context of the com.android.car.updatable
+ private final Context mPackageContext;
+
+ /** Create context for updatable package */
+ public static UpdatablePackageContext create(Context baseContext) {
+ Context packageContext;
+ try {
+ PackageInfo info = baseContext.getPackageManager().getPackageInfo(
+ UPDATABLE_CAR_SERVICE_PACKAGE_NAME, 0);
+ if (info == null || info.applicationInfo == null || !(info.applicationInfo.isSystemApp()
+ || info.applicationInfo.isUpdatedSystemApp())) {
+ throw new IllegalStateException(
+ "Updated car service package is not usable:" + ((info == null)
+ ? "do not exist" : info.applicationInfo));
+ }
+ // CONTEXT_IGNORE_SECURITY: UID is different but ok as the package is trustable system
+ // app
+ packageContext = baseContext.createPackageContext(
+ UPDATABLE_CAR_SERVICE_PACKAGE_NAME,
+ Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot load updatable package code", e);
+ }
+
+ return new UpdatablePackageContext(baseContext, packageContext);
+ }
+
+ private UpdatablePackageContext(Context baseContext, Context packageContext) {
+ super(baseContext);
+ mPackageContext = packageContext;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ return mPackageContext.getAssets();
+ }
+
+ @Override
+ public Resources getResources() {
+ return mPackageContext.getResources();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ // This context cannot load code from builtin any more.
+ return mPackageContext.getClassLoader();
+ }
+}
diff --git a/service-builtin/src/com/android/car/UpdatablePackageDependency.java b/service-builtin/src/com/android/car/UpdatablePackageDependency.java
new file mode 100644
index 0000000000..f26ffb1033
--- /dev/null
+++ b/service-builtin/src/com/android/car/UpdatablePackageDependency.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Declared all dependency into updatable package, mostly for class / method names.
+ *
+ * <p> This is for tracking all dependencies done through java reflection.
+ */
+public class UpdatablePackageDependency {
+ private UpdatablePackageDependency() {}
+
+ /** {@code com.android.car.ProxiedService#doAttachBaseContext()} method */
+ public static final String PROXIED_SERVICE_DO_ATTACH_BASE_CONTEXT = "doAttachBaseContext";
+
+ /** {@code com.android.car.ProxiedService#setBuiltinPackageContext()} method */
+ public static final String PROXIED_SERVICE_SET_BUILTIN_PACKAGE_CONTEXT =
+ "setBuiltinPackageContext";
+
+ /** {@code com.android.car.ProxiedService#doDump()} method */
+ public static final String PROXIED_SERVICE_DO_DUMP = "doDump";
+
+ /** {@code com.android.car.CarServiceImpl} class */
+ public static final String CAR_SERVICE_IMPL_CLASS = "com.android.car.CarServiceImpl";
+
+ /** {@code com.android.car.PerUserCarServiceImpl} class */
+ public static final String PER_USER_CAR_SERVICE_IMPL_CLASS =
+ "com.android.car.PerUserCarServiceImpl";
+
+ /** {@code com.android.car.pm.CarSafetyAccessibilityServiceImpl} class */
+ public static final String CAR_ACCESSIBILITY_IMPL_CLASS =
+ "com.android.car.pm.CarSafetyAccessibilityServiceImpl";
+
+ /**
+ * {@code com.android.car.pm.CarSafetyAccessibilityServiceImpl#onAccessibilityEvent()} method
+ */
+ public static final String CAR_ACCESSIBILITY_ON_ACCESSIBILITY_EVENT = "onAccessibilityEvent";
+}
diff --git a/service-builtin/src/com/android/car/admin/FactoryResetActivity.java b/service-builtin/src/com/android/car/admin/FactoryResetActivity.java
new file mode 100644
index 0000000000..61640b3c6c
--- /dev/null
+++ b/service-builtin/src/com/android/car/admin/FactoryResetActivity.java
@@ -0,0 +1,190 @@
+/*
+ * 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.admin;
+
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
+
+import static com.android.car.admin.NotificationHelper.FACTORY_RESET_NOTIFICATION_ID;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.car.Car;
+import android.car.builtin.util.Slog;
+import android.car.drivingstate.CarDrivingStateEvent;
+import android.car.drivingstate.CarDrivingStateManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+import com.android.internal.os.IResultReceiver;
+
+// TODO(b/171603586): STOPSHIP move this class to CarSettings
+/**
+ * Activity shown when a factory request is imminent, it gives the user the option to reset now or
+ * wait until the device is rebooted / resumed from suspend.
+ */
+public final class FactoryResetActivity extends Activity {
+
+ private static final String TAG = CarLog.tagFor(FactoryResetActivity.class);
+
+ public static final String EXTRA_CALLBACK = "factory_reset_callback";
+
+ private Button mNowButton;
+ private Button mLaterButton;
+ private IResultReceiver mCallback;
+
+ private Car mCar;
+ private CarDrivingStateManager mCarDrivingStateManager;
+ /**
+ * Sends the notification warning the user about the factory reset.
+ */
+ public static void sendNotification(Context context, IResultReceiver callback) {
+ // The factory request is received by CarService - which runs on system user - but the
+ // notification will be sent to all users.
+ UserHandle currentUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+ @SuppressWarnings("deprecation")
+ Intent intent = new Intent(context, FactoryResetActivity.class)
+ .putExtra(EXTRA_CALLBACK, callback.asBinder());
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
+ FACTORY_RESET_NOTIFICATION_ID, intent, PendingIntent.FLAG_IMMUTABLE,
+ /* options= */ null, currentUser);
+
+ // TODO (b/13679261) allows OEM to customize the package name shown in notification
+ Notification notification = NotificationHelper
+ .newNotificationBuilder(context, NotificationManager.IMPORTANCE_HIGH)
+ .setSmallIcon(R.drawable.car_ic_warning)
+ .setColor(context.getColor(R.color.red_warning))
+ .setContentTitle(context.getString(R.string.factory_reset_notification_title))
+ .setContentText(context.getString(R.string.factory_reset_notification_text))
+ .setCategory(Notification.CATEGORY_CAR_WARNING)
+ .setOngoing(true)
+ .addAction(/* icon= */ 0,
+ context.getString(R.string.factory_reset_notification_button),
+ pendingIntent)
+ .build();
+
+ Slog.i(TAG, "Showing factory reset notification on all users");
+ context.getSystemService(NotificationManager.class)
+ .notifyAsUser(TAG, FACTORY_RESET_NOTIFICATION_ID, notification, UserHandle.ALL);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ Object binder = null;
+
+ try {
+ binder = intent.getExtra(EXTRA_CALLBACK);
+ mCallback = IResultReceiver.Stub.asInterface((IBinder) binder);
+ } catch (Exception e) {
+ Slog.w(TAG, "error getting IResultReveiver from " + EXTRA_CALLBACK + " extra ("
+ + binder + ") on " + intent, e);
+ }
+
+ if (mCallback == null) {
+ Slog.wtf(TAG, "no IResultReceiver / " + EXTRA_CALLBACK + " extra on " + intent);
+ finish();
+ return;
+ }
+
+ // Connect to car service
+ mCar = Car.createCar(this);
+ mCarDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
+ Car.CAR_DRIVING_STATE_SERVICE);
+ showMore();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ private void showMore() {
+ CarDrivingStateEvent state = mCarDrivingStateManager.getCurrentCarDrivingState();
+ switch (state.eventValue) {
+ case DRIVING_STATE_PARKED:
+ showFactoryResetDialog();
+ break;
+ default:
+ showFactoryResetToast();
+ }
+ }
+
+ private void showFactoryResetDialog() {
+ // TODO(b/171603586): STOPSHIP use Chassis library after moving this class to CarSettings
+ AlertDialog dialog = new AlertDialog.Builder(/* context= */ this,
+ com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert)
+ .setTitle(R.string.factory_reset_parked_title)
+ .setMessage(R.string.factory_reset_parked_text)
+ .setPositiveButton(R.string.factory_reset_later_button,
+ (d, which) -> factoryResetLater())
+ .setNegativeButton(R.string.factory_reset_now_button,
+ (d, which) -> factoryResetNow())
+ .setCancelable(false)
+ .setOnDismissListener((d) -> finish())
+ .create();
+
+ dialog.show();
+ }
+
+ private void showFactoryResetToast() {
+ showToast(R.string.factory_reset_driving_text);
+ finish();
+ }
+
+ private void factoryResetNow() {
+ Slog.i(TAG, "Factory reset acknowledged; finishing it");
+
+ try {
+ mCallback.send(/* resultCode= */ 0, /* resultData= */ null);
+
+ // Cancel pending intent and notification
+ getSystemService(NotificationManager.class).cancel(FACTORY_RESET_NOTIFICATION_ID);
+ PendingIntent.getActivity(this, FACTORY_RESET_NOTIFICATION_ID, getIntent(),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT).cancel();
+ } catch (Exception e) {
+ Slog.e(TAG, "error factory resetting or cancelling notification / intent", e);
+ return;
+ } finally {
+ finish();
+ }
+ }
+
+ private void factoryResetLater() {
+ Slog.i(TAG, "Delaying factory reset.");
+ showToast(R.string.factory_reset_later_text);
+ finish();
+ }
+
+ private void showToast(int resId) {
+ Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/service-builtin/src/com/android/car/admin/NewUserDisclaimerActivity.java b/service-builtin/src/com/android/car/admin/NewUserDisclaimerActivity.java
new file mode 100644
index 0000000000..39cb9fe460
--- /dev/null
+++ b/service-builtin/src/com/android/car/admin/NewUserDisclaimerActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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.admin;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.car.builtin.util.Slog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+import com.android.car.admin.ui.ManagedDeviceTextView;
+import com.android.internal.annotations.VisibleForTesting;
+
+// TODO(b/171603586): STOPSHIP move UI related activities to CarSettings
+/**
+ * Shows a disclaimer when a new user is added in a device that is managed by a device owner.
+ */
+public final class NewUserDisclaimerActivity extends Activity {
+ private static final boolean DEBUG = false;
+ private static final String TAG = CarLog.tagFor(NewUserDisclaimerActivity.class);
+ private static final int NOTIFICATION_ID =
+ NotificationHelper.NEW_USER_DISCLAIMER_NOTIFICATION_ID;
+
+ private Button mAcceptButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.new_user_disclaimer);
+
+ mAcceptButton = findViewById(R.id.accept_button);
+ mAcceptButton.setOnClickListener((v) -> accept());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (DEBUG) Slog.d(TAG, "showing UI");
+
+ PerUserCarDevicePolicyService.getInstance(this).setShown();
+
+ // TODO(b/175057848): automotically finish the activity at x ms if the user doesn't ack it
+ // and/or integrate it with UserNoticeService
+ }
+
+ @VisibleForTesting
+ Button getAcceptButton() {
+ return mAcceptButton;
+ }
+
+ private void accept() {
+ if (DEBUG) Slog.d(TAG, "user accepted");
+
+ PerUserCarDevicePolicyService.getInstance(this).setAcknowledged();
+ finish();
+ }
+
+ static void showNotification(Context context) {
+ PendingIntent pendingIntent = getPendingIntent(context, /* extraFlags= */ 0);
+
+ Notification notification = NotificationHelper
+ .newNotificationBuilder(context, NotificationManager.IMPORTANCE_DEFAULT)
+ // TODO(b/175057848): proper icon?
+ .setSmallIcon(R.drawable.car_ic_mode)
+ .setContentTitle(context.getString(R.string.new_user_managed_notification_title))
+ .setContentText(ManagedDeviceTextView.getManagedDeviceText(context))
+ .setCategory(Notification.CATEGORY_CAR_INFORMATION)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .build();
+
+ if (DEBUG) {
+ Slog.d(TAG, "Showing new managed notification (id " + NOTIFICATION_ID + " on user "
+ + context.getUserId());
+ }
+ context.getSystemService(NotificationManager.class).notify(NOTIFICATION_ID, notification);
+ }
+
+ static void cancelNotification(Context context) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling notification " + NOTIFICATION_ID + " for user "
+ + context.getUserId());
+ }
+ context.getSystemService(NotificationManager.class).cancel(NOTIFICATION_ID);
+ getPendingIntent(context, PendingIntent.FLAG_UPDATE_CURRENT).cancel();
+ }
+
+ @VisibleForTesting
+ static PendingIntent getPendingIntent(Context context, int extraFlags) {
+ return PendingIntent.getActivity(context, NOTIFICATION_ID,
+ new Intent(context, NewUserDisclaimerActivity.class),
+ PendingIntent.FLAG_IMMUTABLE | extraFlags);
+ }
+}
diff --git a/service-builtin/src/com/android/car/admin/NotificationHelper.java b/service-builtin/src/com/android/car/admin/NotificationHelper.java
new file mode 100644
index 0000000000..57f3d7ed78
--- /dev/null
+++ b/service-builtin/src/com/android/car/admin/NotificationHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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.admin;
+
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.car.R;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+// TODO: move this class to CarSettings or at least to some common package (not admin)
+/**
+ * Helper for notification-related tasks
+ */
+public final class NotificationHelper {
+
+ static final int FACTORY_RESET_NOTIFICATION_ID = 42;
+
+ static final int NEW_USER_DISCLAIMER_NOTIFICATION_ID = 108;
+
+ @VisibleForTesting
+ static final String CHANNEL_ID_DEFAULT = "channel_id_default";
+ @VisibleForTesting
+ static final String CHANNEL_ID_HIGH = "channel_id_high";
+
+ /**
+ * Creates a notification (and its notification channel) for the given importance type, setting
+ * its name to be {@code Android System}.
+ *
+ * @param context context for showing the notification
+ * @param importance notification importance. Currently only
+ * {@link NotificationManager.IMPORTANCE_HIGH} is supported.
+ */
+ @NonNull
+ public static Notification.Builder newNotificationBuilder(Context context,
+ @NotificationManager.Importance int importance) {
+ Objects.requireNonNull(context, "context cannot be null");
+
+ String channelId, importanceName;
+ switch (importance) {
+ case NotificationManager.IMPORTANCE_DEFAULT:
+ channelId = CHANNEL_ID_DEFAULT;
+ importanceName = context.getString(R.string.importance_default);
+ break;
+ case NotificationManager.IMPORTANCE_HIGH:
+ channelId = CHANNEL_ID_HIGH;
+ importanceName = context.getString(R.string.importance_high);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported importance: " + importance);
+ }
+ NotificationManager notificationMgr = context.getSystemService(NotificationManager.class);
+ notificationMgr.createNotificationChannel(
+ new NotificationChannel(channelId, importanceName, importance));
+
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ context.getString(com.android.internal.R.string.android_system_label));
+
+ return new Notification.Builder(context, channelId).addExtras(extras);
+ }
+
+ @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
+ details = "private constructor")
+ private NotificationHelper() {
+ throw new UnsupportedOperationException("Contains only static methods");
+ }
+}
diff --git a/service-builtin/src/com/android/car/admin/PerUserCarDevicePolicyService.java b/service-builtin/src/com/android/car/admin/PerUserCarDevicePolicyService.java
new file mode 100644
index 0000000000..704d742167
--- /dev/null
+++ b/service-builtin/src/com/android/car/admin/PerUserCarDevicePolicyService.java
@@ -0,0 +1,189 @@
+/*
+ * 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.admin;
+
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
+import android.annotation.IntDef;
+import android.app.admin.DevicePolicyManager;
+import android.car.builtin.util.Slog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.DebugUtils;
+import android.util.IndentingPrintWriter;
+
+import com.android.car.CarLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * User-specific {@code CarDevicePolicyManagerService}.
+ */
+public final class PerUserCarDevicePolicyService {
+
+ private static final String TAG = CarLog.tagFor(PerUserCarDevicePolicyService.class);
+ private static final boolean DEBUG = false;
+
+ private static final String PREFIX_NEW_USER_DISCLAIMER_STATUS = "NEW_USER_DISCLAIMER_STATUS_";
+
+ // TODO(b/175057848) must be public because of DebugUtils.constantToString()
+ public static final int NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED = 0;
+ public static final int NEW_USER_DISCLAIMER_STATUS_RECEIVED = 1;
+ public static final int NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT = 2;
+ public static final int NEW_USER_DISCLAIMER_STATUS_SHOWN = 3;
+ public static final int NEW_USER_DISCLAIMER_STATUS_ACKED = 4;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = { PREFIX_NEW_USER_DISCLAIMER_STATUS }, value = {
+ NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED,
+ NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT,
+ NEW_USER_DISCLAIMER_STATUS_RECEIVED,
+ NEW_USER_DISCLAIMER_STATUS_SHOWN,
+ NEW_USER_DISCLAIMER_STATUS_ACKED
+ })
+ public @interface NewUserDisclaimerStatus {}
+
+ private static final Object SLOCK = new Object();
+
+ @GuardedBy("SLOCK")
+ private static PerUserCarDevicePolicyService sInstance;
+
+ private final Context mContext;
+
+ @GuardedBy("sLock")
+ @NewUserDisclaimerStatus
+ private int mNewUserDisclaimerStatus = NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received intent on user " + mContext.getUserId() + ": " + intent);
+ }
+ switch(intent.getAction()) {
+ case DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER:
+ setUserDisclaimerStatus(NEW_USER_DISCLAIMER_STATUS_RECEIVED);
+ showNewUserDisclaimer();
+ break;
+ default:
+ Slog.w(TAG, "received unexpected intent: " + intent);
+ }
+ }
+ };
+
+ /**
+ * Gests the singleton instance, creating it if necessary.
+ */
+ public static PerUserCarDevicePolicyService getInstance(Context context) {
+ Objects.requireNonNull(context, "context cannot be null");
+
+ synchronized (SLOCK) {
+ if (sInstance == null) {
+ sInstance = new PerUserCarDevicePolicyService(context.getApplicationContext());
+ if (DEBUG) Slog.d(TAG, "Created instance: " + sInstance);
+ }
+
+ return sInstance;
+ }
+ }
+
+ @VisibleForTesting
+ PerUserCarDevicePolicyService(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Callback for when the service is created.
+ */
+ public void onCreate() {
+ if (DEBUG) Slog.d(TAG, "registering BroadcastReceiver");
+
+ mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(
+ DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER));
+ }
+
+ /**
+ * Callback for when the service is not needed anymore.
+ */
+ public void onDestroy() {
+ synchronized (SLOCK) {
+ sInstance = null;
+ }
+ if (DEBUG) Slog.d(TAG, "unregistering BroadcastReceiver");
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ /**
+ * Dump its contents.
+ */
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
+ public void dump(IndentingPrintWriter pw) {
+ synchronized (SLOCK) {
+ pw.printf("mNewUserDisclaimerStatus: %s\n",
+ newUserDisclaimerStatusToString(mNewUserDisclaimerStatus));
+ }
+ }
+
+ void setShown() {
+ setUserDisclaimerStatus(NEW_USER_DISCLAIMER_STATUS_SHOWN);
+ }
+
+ void setAcknowledged() {
+ setUserDisclaimerStatus(NEW_USER_DISCLAIMER_STATUS_ACKED);
+ NewUserDisclaimerActivity.cancelNotification(mContext);
+
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ dpm.resetNewUserDisclaimer();
+ }
+
+ private void showNewUserDisclaimer() {
+ // TODO(b/175057848) persist status so it's shown again if car service crashes?
+ NewUserDisclaimerActivity.showNotification(mContext);
+ setUserDisclaimerStatus(NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT);
+ }
+
+ private void setUserDisclaimerStatus(@NewUserDisclaimerStatus int status) {
+ synchronized (SLOCK) {
+ if (DEBUG) {
+ Slog.d(TAG, "Changinging status from "
+ + newUserDisclaimerStatusToString(mNewUserDisclaimerStatus) + " to "
+ + newUserDisclaimerStatusToString(status));
+ }
+ mNewUserDisclaimerStatus = status;
+ }
+ }
+
+ @VisibleForTesting
+ @NewUserDisclaimerStatus
+ int getNewUserDisclaimerStatus() {
+ synchronized (SLOCK) {
+ return mNewUserDisclaimerStatus;
+ }
+ }
+
+ @VisibleForTesting
+ static String newUserDisclaimerStatusToString(@NewUserDisclaimerStatus int status) {
+ return DebugUtils.constantToString(PerUserCarDevicePolicyService.class,
+ PREFIX_NEW_USER_DISCLAIMER_STATUS, status);
+ }
+}
diff --git a/service-builtin/src/com/android/car/am/ContinuousBlankActivity.java b/service-builtin/src/com/android/car/am/ContinuousBlankActivity.java
new file mode 100644
index 0000000000..9a4817c284
--- /dev/null
+++ b/service-builtin/src/com/android/car/am/ContinuousBlankActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.am;
+
+import android.app.Activity;
+import android.car.builtin.util.Slog;
+import android.os.Bundle;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+
+/**
+ * Activity to block top activity after suspend to RAM in case of guest user.
+ *
+ * <p> Guest user resuming will cause user switch to another guest user.
+ * For better user experience, no screen from previous user should be displayed.
+ */
+
+public class ContinuousBlankActivity extends Activity {
+ private static final String TAG = CarLog.tagFor(ContinuousBlankActivity.class);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_continuous_blank);
+ Slog.i(TAG, "ContinuousBlankActivity created:");
+ }
+}
diff --git a/service-builtin/src/com/android/car/pm/ActivityBlockingActivity.java b/service-builtin/src/com/android/car/pm/ActivityBlockingActivity.java
new file mode 100644
index 0000000000..9d148e160b
--- /dev/null
+++ b/service-builtin/src/com/android/car/pm/ActivityBlockingActivity.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2016 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.pm;
+
+import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME;
+import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID;
+import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO;
+import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.IActivityManager;
+import android.car.Car;
+import android.car.builtin.util.Slog;
+import android.car.content.pm.CarPackageManager;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.opengl.GLSurfaceView;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+import com.android.car.pm.blurredbackground.BlurredSurfaceRenderer;
+
+import java.util.List;
+
+/**
+ * Default activity that will be launched when the current foreground activity is not allowed.
+ * Additional information on blocked Activity should be passed as intent extras.
+ */
+public class ActivityBlockingActivity extends Activity {
+ private static final int EGL_CONTEXT_VERSION = 3;
+ private static final int EGL_CONFIG_SIZE = 8;
+ private static final int INVALID_TASK_ID = -1;
+ private final Object mLock = new Object();
+
+ private GLSurfaceView mGLSurfaceView;
+ private BlurredSurfaceRenderer mSurfaceRenderer;
+ private boolean mIsGLSurfaceSetup = false;
+
+ private Car mCar;
+ private CarUxRestrictionsManager mUxRManager;
+ private CarPackageManager mCarPackageManager;
+
+ private Button mExitButton;
+ private Button mToggleDebug;
+
+ private int mBlockedTaskId;
+ private IActivityManager mAm;
+
+ private final View.OnClickListener mOnExitButtonClickedListener =
+ v -> {
+ if (isExitOptionCloseApplication()) {
+ handleCloseApplication();
+ } else {
+ handleRestartingTask();
+ }
+ };
+
+ private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mToggleDebug.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ updateButtonWidths();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_blocking);
+
+ mExitButton = findViewById(R.id.exit_button);
+ mAm = ActivityManager.getService();
+
+ // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the
+ // restrictions are lifted.
+ // This Activity should be launched only after car service is initialized. Currently this
+ // Activity is only launched from CPMS. So this is safe to do.
+ mCar = Car.createCar(this, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+ (car, ready) -> {
+ if (!ready) {
+ return;
+ }
+ mCarPackageManager = (CarPackageManager) car.getCarManager(
+ Car.PACKAGE_SERVICE);
+ mUxRManager = (CarUxRestrictionsManager) car.getCarManager(
+ Car.CAR_UX_RESTRICTION_SERVICE);
+ // This activity would have been launched only in a restricted state.
+ // But ensuring when the service connection is established, that we are still
+ // in a restricted state.
+ handleUxRChange(mUxRManager.getCurrentCarUxRestrictions());
+ mUxRManager.registerListener(ActivityBlockingActivity.this::handleUxRChange);
+ });
+
+ setupGLSurface();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mIsGLSurfaceSetup) {
+ mGLSurfaceView.onResume();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Display info about the current blocked activity, and optionally show an exit button
+ // to restart the blocked task (stack of activities) if its root activity is DO.
+ mBlockedTaskId = getIntent().getIntExtra(BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID,
+ INVALID_TASK_ID);
+
+ // blockedActivity is expected to be always passed in as the topmost activity of task.
+ String blockedActivity = getIntent().getStringExtra(
+ BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
+ if (!TextUtils.isEmpty(blockedActivity)) {
+ if (isTopActivityBehindAbaDistractionOptimized()) {
+ Slog.e(CarLog.TAG_AM, "Top activity is already DO, so finishing");
+ finish();
+ return;
+ }
+
+ if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+ Slog.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
+ }
+ }
+
+ displayExitButton();
+
+ // Show more debug info for non-user build.
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ displayDebugInfo();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (mIsGLSurfaceSetup) {
+ // We queue this event so that it runs on the Rendering thread
+ mGLSurfaceView.queueEvent(() -> mSurfaceRenderer.onPause());
+
+ mGLSurfaceView.onPause();
+ }
+
+ // Finish when blocking activity goes invisible to avoid it accidentally re-surfaces with
+ // stale string regarding blocked activity.
+ finish();
+ }
+
+ private void setupGLSurface() {
+ DisplayManager displayManager = (DisplayManager) getApplicationContext().getSystemService(
+ Context.DISPLAY_SERVICE);
+ DisplayInfo displayInfo = new DisplayInfo();
+
+ int displayId = getDisplayId();
+ displayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+
+ Rect windowRect = getAppWindowRect();
+
+ // We currently don't support blur for secondary display
+ // (because it is hard to take a screenshot of a secondary display)
+ // So for secondary displays, the GLSurfaceView will not appear blurred
+ boolean shouldRenderBlurred = getDisplayId() == Display.DEFAULT_DISPLAY;
+
+ mSurfaceRenderer = new BlurredSurfaceRenderer(this, windowRect, shouldRenderBlurred);
+
+ mGLSurfaceView = findViewById(R.id.blurred_surface_view);
+ mGLSurfaceView.setEGLContextClientVersion(EGL_CONTEXT_VERSION);
+
+ // Sets up the surface so that we can make it translucent if needed
+ mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ mGLSurfaceView.setEGLConfigChooser(EGL_CONFIG_SIZE, EGL_CONFIG_SIZE, EGL_CONFIG_SIZE,
+ EGL_CONFIG_SIZE, EGL_CONFIG_SIZE, EGL_CONFIG_SIZE);
+
+ mGLSurfaceView.setRenderer(mSurfaceRenderer);
+
+ // We only want to render the screen once
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+
+ mIsGLSurfaceSetup = true;
+ }
+
+ /**
+ * Computes a Rect that represents the portion of the screen that contains the activity that is
+ * being blocked.
+ *
+ * @return Rect that represents the application window
+ */
+ private Rect getAppWindowRect() {
+ Insets systemBarInsets = getWindowManager()
+ .getCurrentWindowMetrics()
+ .getWindowInsets()
+ .getInsets(WindowInsets.Type.systemBars());
+
+ Rect displayBounds = getWindowManager().getCurrentWindowMetrics().getBounds();
+
+ int leftX = systemBarInsets.left;
+ int rightX = displayBounds.width() - systemBarInsets.right;
+ int topY = systemBarInsets.top;
+ int bottomY = displayBounds.height() - systemBarInsets.bottom;
+
+ return new Rect(leftX, topY, rightX, bottomY);
+ }
+
+ private void displayExitButton() {
+ String exitButtonText = getExitButtonText();
+
+ mExitButton.setText(exitButtonText);
+ mExitButton.setOnClickListener(mOnExitButtonClickedListener);
+ }
+
+ // If the root activity is DO, the user will have the option to go back to that activity,
+ // otherwise, the user will have the option to close the blocked application
+ private boolean isExitOptionCloseApplication() {
+ boolean isRootDO = getIntent().getBooleanExtra(
+ BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, false);
+ return mBlockedTaskId == INVALID_TASK_ID || !isRootDO;
+ }
+
+ private String getExitButtonText() {
+ return isExitOptionCloseApplication() ? getString(R.string.exit_button_close_application)
+ : getString(R.string.exit_button_go_back);
+ }
+
+ /**
+ * It is possible that the stack info has changed between when the intent to launch this
+ * activity was initiated and when this activity is started. Check whether the activity behind
+ * the ABA is distraction optimized.
+ */
+ private boolean isTopActivityBehindAbaDistractionOptimized() {
+ List<RootTaskInfo> taskInfos;
+ try {
+ taskInfos = mAm.getAllRootTaskInfos();
+ } catch (RemoteException e) {
+ Slog.e(CarLog.TAG_AM, "Unable to get stack info from ActivityManager");
+ // assume that the state is still correct, the activity behind is not DO
+ return false;
+ }
+
+ RootTaskInfo topStackBehindAba = null;
+ for (RootTaskInfo taskInfo : taskInfos) {
+ if (taskInfo.displayId != getDisplayId()) {
+ // ignore stacks on other displays
+ continue;
+ }
+
+ if (getComponentName().equals(taskInfo.topActivity)) {
+ // ignore stack with the blocking activity
+ continue;
+ }
+
+ if (!taskInfo.visible) {
+ // ignore stacks that aren't visible
+ continue;
+ }
+
+ if (topStackBehindAba == null || topStackBehindAba.position < taskInfo.position) {
+ topStackBehindAba = taskInfo;
+ }
+ }
+
+ if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+ Slog.d(CarLog.TAG_AM, String.format("Top stack behind ABA is: %s", topStackBehindAba));
+ }
+
+ if (topStackBehindAba != null && topStackBehindAba.topActivity != null) {
+ boolean isDo = mCarPackageManager.isActivityDistractionOptimized(
+ topStackBehindAba.topActivity.getPackageName(),
+ topStackBehindAba.topActivity.getClassName());
+ if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+ Slog.d(CarLog.TAG_AM,
+ String.format("Top activity (%s) is DO: %s", topStackBehindAba.topActivity,
+ isDo));
+ }
+ return isDo;
+ }
+
+ // unknown top stack / activity, default to considering it non-DO
+ return false;
+ }
+
+ private void displayDebugInfo() {
+ String blockedActivity = getIntent().getStringExtra(
+ BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
+ String rootActivity = getIntent().getStringExtra(BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME);
+
+ TextView debugInfo = findViewById(R.id.debug_info);
+ debugInfo.setText(getDebugInfo(blockedActivity, rootActivity));
+
+ // We still want to ensure driving safety for non-user build;
+ // toggle visibility of debug info with this button.
+ mToggleDebug = findViewById(R.id.toggle_debug_info);
+ mToggleDebug.setVisibility(View.VISIBLE);
+ mToggleDebug.setOnClickListener(v -> {
+ boolean isDebugVisible = debugInfo.getVisibility() == View.VISIBLE;
+ debugInfo.setVisibility(isDebugVisible ? View.GONE : View.VISIBLE);
+ });
+
+ mToggleDebug.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
+ }
+
+ // When the Debug button is visible, we set both of the visible buttons to have the width
+ // of whichever button is wider
+ private void updateButtonWidths() {
+ Button debugButton = findViewById(R.id.toggle_debug_info);
+
+ int exitButtonWidth = mExitButton.getWidth();
+ int debugButtonWidth = debugButton.getWidth();
+
+ if (exitButtonWidth > debugButtonWidth) {
+ debugButton.setWidth(exitButtonWidth);
+ } else {
+ mExitButton.setWidth(debugButtonWidth);
+ }
+ }
+
+ private String getDebugInfo(String blockedActivity, String rootActivity) {
+ StringBuilder debug = new StringBuilder();
+
+ ComponentName blocked = ComponentName.unflattenFromString(blockedActivity);
+ debug.append("Blocked activity is ")
+ .append(blocked.getShortClassName())
+ .append("\nBlocked activity package is ")
+ .append(blocked.getPackageName());
+
+ if (rootActivity != null) {
+ ComponentName root = ComponentName.unflattenFromString(rootActivity);
+ // Optionally show root activity info if it differs from the blocked activity.
+ if (!root.equals(blocked)) {
+ debug.append("\n\nRoot activity is ").append(root.getShortClassName());
+ }
+ if (!root.getPackageName().equals(blocked.getPackageName())) {
+ debug.append("\nRoot activity package is ").append(root.getPackageName());
+ }
+ }
+ return debug.toString();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCar.disconnect();
+ mUxRManager.unregisterListener();
+ if (mToggleDebug != null) {
+ mToggleDebug.getViewTreeObserver().removeOnGlobalLayoutListener(
+ mOnGlobalLayoutListener);
+ }
+ mCar.disconnect();
+ }
+
+ // If no distraction optimization is required in the new restrictions, then dismiss the
+ // blocking activity (self).
+ private void handleUxRChange(CarUxRestrictions restrictions) {
+ if (restrictions == null) {
+ return;
+ }
+ if (!restrictions.isRequiresDistractionOptimization()) {
+ finish();
+ }
+ }
+
+ private void handleCloseApplication() {
+ if (isFinishing()) {
+ return;
+ }
+
+ Intent startMain = new Intent(Intent.ACTION_MAIN);
+ startMain.addCategory(Intent.CATEGORY_HOME);
+ startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(startMain);
+ finish();
+ }
+
+ private void handleRestartingTask() {
+ // Lock on self to avoid restarting the same task twice.
+ synchronized (mLock) {
+ if (isFinishing()) {
+ return;
+ }
+
+ if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+ Slog.i(CarLog.TAG_AM, "Restarting task " + mBlockedTaskId);
+ }
+ mCarPackageManager.restartTask(mBlockedTaskId);
+ finish();
+ }
+ }
+}
diff --git a/service-builtin/src/com/android/car/pm/CarSafetyAccessibilityService.java b/service-builtin/src/com/android/car/pm/CarSafetyAccessibilityService.java
new file mode 100644
index 0000000000..321241b464
--- /dev/null
+++ b/service-builtin/src/com/android/car/pm/CarSafetyAccessibilityService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.pm;
+
+import android.accessibilityservice.AccessibilityService;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.car.ServiceProxy;
+import com.android.car.UpdatablePackageContext;
+import com.android.car.UpdatablePackageDependency;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/** Proxy for CarSafetyAccessibilityServiceImpl */
+public class CarSafetyAccessibilityService extends AccessibilityService {
+ private final ServiceProxy mProxy = new ServiceProxy(
+ UpdatablePackageDependency.CAR_ACCESSIBILITY_IMPL_CLASS);
+
+ private UpdatablePackageContext mUpdatablePackageContext;
+ private Object mImpl;
+ private Method mOnAccessibilityEventMethod;
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ try {
+ mOnAccessibilityEventMethod.invoke(mImpl, new Object[]{event});
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot call onAccessibilityEvent", e);
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ }
+
+ @Override
+ public void onCreate() {
+ UpdatablePackageContext updatablePackageContext = UpdatablePackageContext.create(this);
+ try {
+ Class implClass = updatablePackageContext.getClassLoader().loadClass(
+ UpdatablePackageDependency.CAR_ACCESSIBILITY_IMPL_CLASS);
+ // Use default constructor always
+ Constructor constructor = implClass.getConstructor();
+ mImpl = constructor.newInstance();
+ mOnAccessibilityEventMethod = implClass.getMethod(
+ UpdatablePackageDependency.CAR_ACCESSIBILITY_ON_ACCESSIBILITY_EVENT,
+ new Class[]{AccessibilityEvent.class});
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot load impl class", e);
+ }
+ }
+}
diff --git a/service-builtin/src/com/android/car/pm/blurredbackground/BlurTextureProgram.java b/service-builtin/src/com/android/car/pm/blurredbackground/BlurTextureProgram.java
new file mode 100644
index 0000000000..f5ee937db1
--- /dev/null
+++ b/service-builtin/src/com/android/car/pm/blurredbackground/BlurTextureProgram.java
@@ -0,0 +1,337 @@
+/*
+ * 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.pm.blurredbackground;
+
+import android.graphics.Rect;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES30;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * A class containing the OpenGL programs used to render a blurred texture
+ */
+public class BlurTextureProgram {
+
+ private static final float[] FRAME_COORDS = {
+ -1.0f, -1.0f, // 0 bottom left
+ 1.0f, -1.0f, // 1 bottom right
+ -1.0f, 1.0f, // 2 top left
+ 1.0f, 1.0f, // 3 top right
+ };
+ private static final float[] TEXTURE_COORDS = {
+ 0.0f, 0.0f, // 0 bottom left
+ 1.0f, 0.0f, // 1 bottom right
+ 0.0f, 1.0f, // 2 top left
+ 1.0f, 1.0f // 3 top right
+ };
+
+ private static final float[] INVERTED_TEXTURE_COORDS = {
+ 0.0f, 1.0f, // 0 bottom left
+ 1.0f, 1.0f, // 1 bottom right
+ 0.0f, 0.0f, // 2 top left
+ 1.0f, 0.0f // 3 top right
+ };
+
+ private static final int SIZEOF_FLOAT = 4;
+ private static final int NUM_COORDS_PER_VERTEX = 2;
+ private static final float BLUR_RADIUS = 40.0f;
+
+ private final String mVertexShader;
+ private final String mHorizontalBlurShader;
+ private final String mVerticalBlurShader;
+
+ private final int mHorizontalBlurProgram;
+ private final int mVerticalBlurProgram;
+
+ private final int mWidth;
+ private final int mHeight;
+
+ private final int mScreenshotTextureId;
+ private final IntBuffer mScreenshotTextureBuffer;
+ private final float[] mTexMatrix;
+ private final FloatBuffer mResolutionBuffer;
+
+ private final FloatBuffer mVertexBuffer = GLHelper.createFloatBuffer(FRAME_COORDS);
+ private final FloatBuffer mTexBuffer = GLHelper.createFloatBuffer(TEXTURE_COORDS);
+ private final FloatBuffer mInvertedTexBuffer = GLHelper.createFloatBuffer(
+ INVERTED_TEXTURE_COORDS);
+
+ // Locations of the uniforms and attributes for the horizontal program
+ private final int mUHorizontalMVPMatrixLoc;
+ private final int mUHorizontalTexMatrixLoc;
+ private final int mUHorizontalResolutionLoc;
+ private final int mUHorizontalRadiusLoc;
+ private final int mAHorizontalPositionLoc;
+ private final int mAHorizontalTextureCoordLoc;
+
+ // Locations of the uniforms and attributes for the vertical program
+ private final int mUVerticalMVPMatrixLoc;
+ private final int mUVerticalTexMatrixLoc;
+ private final int mUVerticalResolutionLoc;
+ private final int mUVerticalRadiusLoc;
+ private final int mAVerticalPositionLoc;
+ private final int mAVerticalTextureCoordLoc;
+
+ private final IntBuffer mFrameBuffer = IntBuffer.allocate(1);
+ private final IntBuffer mFirstPassTextureBuffer = IntBuffer.allocate(1);
+
+ private int mFrameBufferId;
+ private int mFirstPassTextureId;
+
+ /**
+ * Constructor for the BlurTextureProgram
+ *
+ * @param screenshotTextureBuffer IntBuffer
+ * @param texMatrix Float array used to scale the screenshot texture
+ * @param vertexShader String containing the horizontal blur shader
+ * @param horizontalBlurShader String containing the fragment shader for horizontal
+ * blur
+ * @param verticalBlurShader String containing the fragment shader for vertical blur
+ * @param windowRect Rect representing the location of the window being covered
+ */
+ BlurTextureProgram(
+ IntBuffer screenshotTextureBuffer,
+ float[] texMatrix,
+ String vertexShader,
+ String horizontalBlurShader,
+ String verticalBlurShader,
+ Rect windowRect
+ ) {
+ mVertexShader = vertexShader;
+ mHorizontalBlurShader = horizontalBlurShader;
+ mVerticalBlurShader = verticalBlurShader;
+
+ mScreenshotTextureBuffer = screenshotTextureBuffer;
+ mScreenshotTextureId = screenshotTextureBuffer.get(0);
+ mTexMatrix = texMatrix;
+
+ mHorizontalBlurProgram = GLHelper.createProgram(mVertexShader, mHorizontalBlurShader);
+ mVerticalBlurProgram = GLHelper.createProgram(mVertexShader, mVerticalBlurShader);
+
+ mWidth = windowRect.width();
+ mHeight = windowRect.height();
+
+ mResolutionBuffer = FloatBuffer.wrap(new float[]{(float) mWidth, (float) mHeight, 1.0f});
+
+ // Initialize the uniform and attribute locations for the horizontal blur program
+ mUHorizontalMVPMatrixLoc = GLES30.glGetUniformLocation(mHorizontalBlurProgram,
+ "uMVPMatrix");
+ mUHorizontalTexMatrixLoc = GLES30.glGetUniformLocation(mHorizontalBlurProgram,
+ "uTexMatrix");
+ mUHorizontalResolutionLoc = GLES30.glGetUniformLocation(mHorizontalBlurProgram,
+ "uResolution");
+ mUHorizontalRadiusLoc = GLES30.glGetUniformLocation(mHorizontalBlurProgram, "uRadius");
+
+ mAHorizontalPositionLoc = GLES30.glGetAttribLocation(mHorizontalBlurProgram, "aPosition");
+ mAHorizontalTextureCoordLoc = GLES30.glGetAttribLocation(mHorizontalBlurProgram,
+ "aTextureCoord");
+
+ // Initialize the uniform and attribute locations for the vertical blur program
+ mUVerticalMVPMatrixLoc = GLES30.glGetUniformLocation(mVerticalBlurProgram, "uMVPMatrix");
+ mUVerticalTexMatrixLoc = GLES30.glGetUniformLocation(mVerticalBlurProgram, "uTexMatrix");
+ mUVerticalResolutionLoc = GLES30.glGetUniformLocation(mVerticalBlurProgram, "uResolution");
+ mUVerticalRadiusLoc = GLES30.glGetUniformLocation(mVerticalBlurProgram, "uRadius");
+
+ mAVerticalPositionLoc = GLES30.glGetAttribLocation(mVerticalBlurProgram, "aPosition");
+ mAVerticalTextureCoordLoc = GLES30.glGetAttribLocation(mVerticalBlurProgram,
+ "aTextureCoord");
+ }
+
+ /**
+ * Executes all of the rendering logic. Sets up FrameBuffers and programs to complete two
+ * rendering passes on the captured screenshot to produce a blur.
+ */
+ public void render() {
+ setupProgram(mHorizontalBlurProgram, mScreenshotTextureId,
+ GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+ setHorizontalUniformsAndAttributes();
+
+ // Create the framebuffer that will hold the texture we render to
+ // for the first shader pass
+ mFrameBufferId = GLHelper.createAndBindFramebuffer(mFrameBuffer);
+
+ // Create the empty texture that will store the output of the first shader pass (this'll
+ // be held in the Framebuffer Object)
+ mFirstPassTextureId = GLHelper.createAndBindTextureObject(mFirstPassTextureBuffer,
+ GLES30.GL_TEXTURE_2D);
+
+ setupTextureForFramebuffer(mFirstPassTextureId);
+ assertValidFramebufferStatus();
+ renderToFramebuffer(mFrameBufferId);
+
+ setupProgram(mVerticalBlurProgram, mFirstPassTextureId, GLES30.GL_TEXTURE_2D);
+ setVerticalUniformsAndAttributes();
+
+ renderToSurface();
+ cleanupResources();
+ }
+
+ /**
+ * Cleans up all OpenGL resources used by programs in this class
+ */
+ public void cleanupResources() {
+ deleteFramebufferTexture();
+ deleteFrameBuffer();
+ deletePrograms();
+
+ GLES30.glFlush();
+ }
+
+ /**
+ * Attaches a 2D texture image to the active framebuffer object
+ *
+ * @param textureId The ID of the texture to be attached
+ */
+ private void setupTextureForFramebuffer(int textureId) {
+ GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB, mWidth, mHeight, 0,
+ GLES30.GL_RGB, GLES30.GL_UNSIGNED_BYTE, null);
+ GLHelper.checkGlErrors("glTexImage2D");
+ GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0,
+ GLES30.GL_TEXTURE_2D, textureId, 0);
+ GLHelper.checkGlErrors("glFramebufferTexture2D");
+ }
+
+ /**
+ * Deletes the texture stored in the framebuffer
+ */
+ private void deleteFramebufferTexture() {
+ GLES30.glDeleteTextures(mFirstPassTextureBuffer.capacity(), mFirstPassTextureBuffer);
+ GLHelper.checkGlErrors("glDeleteTextures");
+ }
+
+ /**
+ * Deletes the frame buffers.
+ */
+ private void deleteFrameBuffer() {
+ GLES30.glDeleteBuffers(1, mFrameBuffer);
+ GLHelper.checkGlErrors("glDeleteBuffers");
+ }
+
+ /**
+ * Deletes the GL programs.
+ */
+ private void deletePrograms() {
+ GLES30.glDeleteProgram(mHorizontalBlurProgram);
+ GLHelper.checkGlErrors("glDeleteProgram");
+ GLES30.glDeleteProgram(mVerticalBlurProgram);
+ GLHelper.checkGlErrors("glDeleteProgram");
+ }
+
+ /**
+ * Set all of the Uniform and Attribute variable values for the horizontal blur program
+ */
+ private void setHorizontalUniformsAndAttributes() {
+ GLES30.glUniformMatrix4fv(mUHorizontalMVPMatrixLoc, 1, false, GLHelper.getIdentityMatrix(),
+ 0);
+ GLES30.glUniformMatrix4fv(mUHorizontalTexMatrixLoc, 1, false, mTexMatrix, 0);
+ GLES30.glUniform3fv(mUHorizontalResolutionLoc, 1, mResolutionBuffer);
+ GLES30.glUniform1f(mUHorizontalRadiusLoc, BLUR_RADIUS);
+
+ GLES30.glEnableVertexAttribArray(mAHorizontalPositionLoc);
+ GLES30.glVertexAttribPointer(mAHorizontalPositionLoc, NUM_COORDS_PER_VERTEX,
+ GLES30.GL_FLOAT, false, NUM_COORDS_PER_VERTEX * SIZEOF_FLOAT, mVertexBuffer);
+
+ GLES30.glEnableVertexAttribArray(mAHorizontalTextureCoordLoc);
+ GLES30.glVertexAttribPointer(mAHorizontalTextureCoordLoc, 2,
+ GLES30.GL_FLOAT, false, 2 * SIZEOF_FLOAT, mTexBuffer);
+ }
+
+ /**
+ * Set all of the Uniform and Attribute variable values for the vertical blur program
+ */
+ private void setVerticalUniformsAndAttributes() {
+ GLES30.glUniformMatrix4fv(mUVerticalMVPMatrixLoc, 1, false, GLHelper.getIdentityMatrix(),
+ 0);
+ GLES30.glUniformMatrix4fv(mUVerticalTexMatrixLoc, 1, false, mTexMatrix, 0);
+ GLES30.glUniform3fv(mUVerticalResolutionLoc, 1, mResolutionBuffer);
+ GLES30.glUniform1f(mUVerticalRadiusLoc, BLUR_RADIUS);
+
+ GLES30.glEnableVertexAttribArray(mAVerticalPositionLoc);
+ GLES30.glVertexAttribPointer(mAVerticalPositionLoc, NUM_COORDS_PER_VERTEX,
+ GLES30.GL_FLOAT, false, NUM_COORDS_PER_VERTEX * SIZEOF_FLOAT, mVertexBuffer);
+
+ GLES30.glEnableVertexAttribArray(mAVerticalTextureCoordLoc);
+ GLES30.glVertexAttribPointer(mAVerticalTextureCoordLoc, 2,
+ GLES30.GL_FLOAT, false, 2 * SIZEOF_FLOAT, mInvertedTexBuffer);
+ }
+
+ /**
+ * Sets the program to be used in the next rendering, and binds a texture to it
+ *
+ * @param programId The Id of the program
+ * @param textureId The Id of the texture to be bound
+ * @param textureTarget The type of texture that is being bound
+ */
+ private void setupProgram(int programId, int textureId, int textureTarget) {
+ GLES30.glUseProgram(programId);
+ GLHelper.checkGlErrors("glUseProgram");
+
+ GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+ GLHelper.checkGlErrors("glActiveTexture");
+
+ GLES30.glBindTexture(textureTarget, textureId);
+ GLHelper.checkGlErrors("glBindTexture");
+ }
+
+ /**
+ * Renders to a framebuffer using the current active program
+ *
+ * @param framebufferId The Id of the framebuffer being rendered to
+ */
+ private void renderToFramebuffer(int framebufferId) {
+ GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+ GLHelper.checkGlErrors("glClear");
+
+ GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, framebufferId);
+ GLHelper.checkGlErrors("glBindFramebuffer");
+
+ GLES30.glViewport(0, 0, mWidth, mHeight);
+ GLHelper.checkGlErrors("glViewport");
+
+ GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0,
+ FRAME_COORDS.length / NUM_COORDS_PER_VERTEX);
+ GLHelper.checkGlErrors("glDrawArrays");
+
+ GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+ }
+
+ /**
+ * Renders to a the GLSurface using the current active program
+ */
+ private void renderToSurface() {
+ GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+ GLHelper.checkGlErrors("glDrawArrays");
+
+ GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+ GLHelper.checkGlErrors("glDrawArrays");
+
+ GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0,
+ FRAME_COORDS.length / NUM_COORDS_PER_VERTEX);
+ GLHelper.checkGlErrors("glDrawArrays");
+ }
+
+ private void assertValidFramebufferStatus() {
+ if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER)
+ != GLES30.GL_FRAMEBUFFER_COMPLETE) {
+ throw new RuntimeException(
+ "Failed to attach Framebuffer. Framebuffer status code is: "
+ + GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER));
+ }
+ }
+}
diff --git a/service-builtin/src/com/android/car/pm/blurredbackground/BlurredSurfaceRenderer.java b/service-builtin/src/com/android/car/pm/blurredbackground/BlurredSurfaceRenderer.java
new file mode 100644
index 0000000000..62d085e896
--- /dev/null
+++ b/service-builtin/src/com/android/car/pm/blurredbackground/BlurredSurfaceRenderer.java
@@ -0,0 +1,196 @@
+/*
+ * 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.pm.blurredbackground;
+
+import android.car.builtin.util.Slog;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES30;
+import android.opengl.GLSurfaceView;
+import android.os.IBinder;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+
+import java.nio.IntBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * The renderer class for the {@link GLSurfaceView} of the {@link ActivityBlockingActivity}
+ */
+public class BlurredSurfaceRenderer implements GLSurfaceView.Renderer {
+
+ private static final String TAG = CarLog.tagFor(BlurredSurfaceRenderer.class);
+ private static final int NUM_INDICES_TO_RENDER = 4;
+
+ private final String mVertexShader;
+ private final String mHorizontalBlurShader;
+ private final String mVerticalBlurShader;
+ private final Rect mWindowRect;
+
+ private BlurTextureProgram mProgram;
+ private SurfaceTexture mSurfaceTexture;
+ private Surface mSurface;
+
+ private int mScreenshotTextureId;
+ private final IntBuffer mScreenshotTextureBuffer = IntBuffer.allocate(1);
+ private final float[] mTexMatrix = new float[16];
+
+ private final boolean mShadersLoadedSuccessfully;
+ private final boolean mShouldRenderBlurred;
+ private boolean mIsScreenShotCaptured = false;
+
+ /**
+ * Constructs a new {@link BlurredSurfaceRenderer} and loads the shaders needed for rendering a
+ * blurred texture
+ *
+ * @param windowRect Rect that represents the application window
+ */
+ public BlurredSurfaceRenderer(Context context, Rect windowRect, boolean shouldRenderBlurred) {
+ mShouldRenderBlurred = shouldRenderBlurred;
+
+ mVertexShader = GLHelper.getShaderFromRaw(context, R.raw.vertex_shader);
+ mHorizontalBlurShader = GLHelper.getShaderFromRaw(context,
+ R.raw.horizontal_blur_fragment_shader);
+ mVerticalBlurShader = GLHelper.getShaderFromRaw(context,
+ R.raw.vertical_blur_fragment_shader);
+
+ mShadersLoadedSuccessfully = mVertexShader != null
+ && mHorizontalBlurShader != null
+ && mVerticalBlurShader != null;
+
+ mWindowRect = windowRect;
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ mScreenshotTextureId = GLHelper.createAndBindTextureObject(mScreenshotTextureBuffer,
+ GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+
+ mSurfaceTexture = new SurfaceTexture(mScreenshotTextureId);
+ mSurface = new Surface(mSurfaceTexture);
+
+ if (mShouldRenderBlurred) {
+ mIsScreenShotCaptured = captureScreenshot();
+ }
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ if (shouldDrawFrame()) {
+ mProgram = new BlurTextureProgram(
+ mScreenshotTextureBuffer,
+ mTexMatrix,
+ mVertexShader,
+ mHorizontalBlurShader,
+ mVerticalBlurShader,
+ mWindowRect
+ );
+ mProgram.render();
+ } else {
+ logWillNotRenderBlurredMsg();
+
+ // If we determine we shouldn't render a blurred texture, we
+ // will default to rendering a transparent GLSurfaceView so that
+ // the ActivityBlockingActivity appears translucent
+ renderTransparent();
+ }
+ }
+
+ private void renderTransparent() {
+ GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+ GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, /*first index to render */ 0,
+ NUM_INDICES_TO_RENDER);
+ }
+
+ /**
+ * Called when the ActivityBlockingActivity pauses cleans up the OpenGL program
+ */
+ public void onPause() {
+ if (mProgram != null) {
+ mProgram.cleanupResources();
+ }
+ deleteScreenshotTexture();
+ }
+
+ private boolean captureScreenshot() {
+ boolean isScreenshotCaptured = false;
+
+ try {
+ final IBinder token = SurfaceControl.getInternalDisplayToken();
+ if (token == null) {
+ Slog.e(TAG,
+ "Could not find display token for screenshot. Will not capture screenshot");
+ } else {
+ final SurfaceControl.DisplayCaptureArgs captureArgs =
+ new SurfaceControl.DisplayCaptureArgs.Builder(token)
+ .setSize(mWindowRect.width(), mWindowRect.height())
+ .setSourceCrop(mWindowRect)
+ .setUseIdentityTransform(true)
+ .build();
+
+ SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+ SurfaceControl.captureDisplay(captureArgs);
+ mSurface.attachAndQueueBufferWithColorSpace(
+ screenshotHardwareBuffer.getHardwareBuffer(),
+ screenshotHardwareBuffer.getColorSpace());
+ mSurfaceTexture.updateTexImage();
+ mSurfaceTexture.getTransformMatrix(mTexMatrix);
+ isScreenshotCaptured = true;
+ }
+
+ } finally {
+ mSurface.release();
+ mSurfaceTexture.release();
+ }
+
+ return isScreenshotCaptured;
+ }
+
+ private void deleteScreenshotTexture() {
+ GLES30.glDeleteTextures(mScreenshotTextureBuffer.capacity(), mScreenshotTextureBuffer);
+ GLHelper.checkGlErrors("glDeleteTextures");
+
+ mIsScreenShotCaptured = false;
+ }
+
+ private void logWillNotRenderBlurredMsg() {
+ if (!mIsScreenShotCaptured) {
+ Slog.e(TAG, "Screenshot was not captured. Will not render blurred surface");
+ }
+ if (!mShadersLoadedSuccessfully) {
+ Slog.e(TAG, "Shaders were not loaded successfully. Will not render blurred surface");
+ }
+ }
+
+ private boolean shouldDrawFrame() {
+ return mIsScreenShotCaptured
+ && mShadersLoadedSuccessfully
+ && mShouldRenderBlurred;
+ }
+}
+
diff --git a/service-builtin/src/com/android/car/pm/blurredbackground/GLHelper.java b/service-builtin/src/com/android/car/pm/blurredbackground/GLHelper.java
new file mode 100644
index 0000000000..e005bcc14e
--- /dev/null
+++ b/service-builtin/src/com/android/car/pm/blurredbackground/GLHelper.java
@@ -0,0 +1,195 @@
+/*
+ * 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.pm.blurredbackground;
+
+import android.annotation.Nullable;
+import android.car.builtin.util.Slog;
+import android.content.Context;
+import android.opengl.GLES30;
+import android.opengl.Matrix;
+import android.os.Build;
+
+import com.android.car.CarLog;
+
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * A helper class for simple OpenGL operations
+ */
+public class GLHelper {
+
+ private static final String TAG = CarLog.tagFor(GLHelper.class);
+ private static final int SIZEOF_FLOAT = 4;
+
+ /**
+ * Creates an OpenGL program that uses the provided shader sources and returns the id of the
+ * created program
+ *
+ * @param vertexShaderSource The source for the vertex shader
+ * @param fragmentShaderSource The source for the fragment shader
+ * @return The id of the created program
+ */
+ public static int createProgram(String vertexShaderSource, String fragmentShaderSource) {
+ int vertexShader = compileShader(GLES30.GL_VERTEX_SHADER, vertexShaderSource);
+ int fragmentShader = compileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderSource);
+
+ int programId = GLES30.glCreateProgram();
+ checkGlErrors("glCreateProgram");
+
+ GLES30.glAttachShader(programId, vertexShader);
+ GLES30.glAttachShader(programId, fragmentShader);
+
+ // glDeleteShader flags these shaders to be deleted, the shaders
+ // are not actually deleted until the program they are attached to are deleted
+ GLES30.glDeleteShader(vertexShader);
+ checkGlErrors("glDeleteShader");
+ GLES30.glDeleteShader(fragmentShader);
+ checkGlErrors("glDeleteShader");
+
+ GLES30.glLinkProgram(programId);
+ checkGlErrors("glLinkProgram");
+
+ return programId;
+ }
+
+ /**
+ * Creates and binds a texture and returns the id of the created texture
+ *
+ * @param textureIdBuffer The IntBuffer that will contain the created texture id
+ * @param textureTarget The texture target for the created texture
+ * @return The id of the created and bound texture
+ */
+ public static int createAndBindTextureObject(IntBuffer textureIdBuffer, int textureTarget) {
+ GLES30.glGenTextures(1, textureIdBuffer);
+
+ int textureId = textureIdBuffer.get(0);
+
+ GLES30.glBindTexture(textureTarget, textureId);
+ checkGlErrors("glBindTexture");
+
+ // We define the filters that will be applied to the textures if
+ // they get minified or magnified when they are sampled
+ GLES30.glTexParameterf(textureTarget, GLES30.GL_TEXTURE_MIN_FILTER,
+ GLES30.GL_LINEAR);
+ GLES30.glTexParameterf(textureTarget, GLES30.GL_TEXTURE_MAG_FILTER,
+ GLES30.GL_LINEAR);
+
+ // Set the wrap parameters for if the edges of the texture do not fill the surface
+ GLES30.glTexParameteri(textureTarget, GLES30.GL_TEXTURE_WRAP_S,
+ GLES30.GL_CLAMP_TO_EDGE);
+ GLES30.glTexParameteri(textureTarget, GLES30.GL_TEXTURE_WRAP_T,
+ GLES30.GL_CLAMP_TO_EDGE);
+
+ return textureId;
+ }
+
+ /**
+ * Creates and binds a Framebuffer object
+ *
+ * @param frameBuffer the IntBuffer that will contain the created Framebuffer ID
+ * @return The id of the created and bound Framebuffer
+ */
+ public static int createAndBindFramebuffer(IntBuffer frameBuffer) {
+ GLES30.glGenFramebuffers(1, frameBuffer);
+ checkGlErrors("glGenFramebuffers");
+
+ int frameBufferId = frameBuffer.get(0);
+
+ GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId);
+ checkGlErrors("glBindFramebuffer");
+
+ return frameBufferId;
+ }
+
+ /**
+ * Retrieves a string of an OpenGL shader
+ *
+ * @param id the ID of the raw shader resource
+ * @return The shader script, null if the shader failed to load
+ */
+ public static @Nullable String getShaderFromRaw(Context context, int id) {
+ try {
+ InputStream stream = context.getResources().openRawResource(id);
+ return new String(Streams.readFully(new InputStreamReader(stream)));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to load shader");
+ return null;
+ }
+ }
+
+ /**
+ * Creates a FloatBuffer to hold texture and vertex coordinates
+ *
+ * @param coords The coordinates that will be held in the FloatBuffer
+ * @return a FloatBuffer containing the provided coordinates
+ */
+ public static FloatBuffer createFloatBuffer(float[] coords) {
+ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
+ floatBuffer.put(coords);
+ floatBuffer.position(0);
+ return floatBuffer;
+ }
+
+ /**
+ * @return a float[] representing a 4x4 identity matrix
+ */
+ public static float[] getIdentityMatrix() {
+ float[] identityMatrix = new float[16];
+ Matrix.setIdentityM(identityMatrix, 0);
+ return identityMatrix;
+ }
+
+ /**
+ * Checks for GL errors, logging any errors found
+ *
+ * @param func The name of the most recent GL function called
+ * @return a boolean representing if there was a GL error or not
+ */
+ public static boolean checkGlErrors(String func) {
+ boolean hadError = false;
+ int error;
+
+ while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR) {
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ Slog.e(TAG, func + " failed: error " + error, new Throwable());
+ }
+ hadError = true;
+ }
+ return hadError;
+ }
+
+ private static int compileShader(int shaderType, String shaderSource) {
+ int shader = GLES30.glCreateShader(shaderType);
+ GLES30.glShaderSource(shader, shaderSource);
+
+ GLES30.glCompileShader(shader);
+ checkGlErrors("glCompileShader");
+
+ return shader;
+ }
+}