Merge "Add projection state APIs to UiModeManager and UiModeManagerService."
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.job.controllers.idle;
|
||||
|
||||
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
|
||||
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
@@ -34,7 +36,9 @@ import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.StateControllerProto;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Set;
|
||||
|
||||
/** Class to track device idle state. */
|
||||
public final class DeviceIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
|
||||
private static final String TAG = "JobScheduler.DeviceIdlenessTracker";
|
||||
private static final boolean DEBUG = JobSchedulerService.DEBUG
|
||||
@@ -50,8 +54,10 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
private boolean mIdle;
|
||||
private boolean mScreenOn;
|
||||
private boolean mDockIdle;
|
||||
private boolean mInCarMode;
|
||||
private boolean mProjectionActive;
|
||||
private IdlenessListener mIdleListener;
|
||||
private final UiModeManager.OnProjectionStateChangeListener mOnProjectionStateChangeListener =
|
||||
this::onProjectionStateChanged;
|
||||
|
||||
private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> {
|
||||
handleIdleTrigger();
|
||||
@@ -60,10 +66,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
public DeviceIdlenessTracker() {
|
||||
// At boot we presume that the user has just "interacted" with the
|
||||
// device in some meaningful way.
|
||||
mIdle = false;
|
||||
mScreenOn = true;
|
||||
mDockIdle = false;
|
||||
mInCarMode = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,11 +101,34 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
filter.addAction(Intent.ACTION_DOCK_IDLE);
|
||||
filter.addAction(Intent.ACTION_DOCK_ACTIVE);
|
||||
|
||||
// Car mode
|
||||
filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
|
||||
filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
|
||||
|
||||
context.registerReceiver(this, filter);
|
||||
|
||||
// TODO(b/172579710): Move the callbacks off the main executor and on to
|
||||
// JobSchedulerBackgroundThread.getExecutor() once synchronization is fixed in this class.
|
||||
context.getSystemService(UiModeManager.class).addOnProjectionStateChangeListener(
|
||||
UiModeManager.PROJECTION_TYPE_ALL, context.getMainExecutor(),
|
||||
mOnProjectionStateChangeListener);
|
||||
}
|
||||
|
||||
private void onProjectionStateChanged(@UiModeManager.ProjectionType int activeProjectionTypes,
|
||||
Set<String> projectingPackages) {
|
||||
boolean projectionActive = activeProjectionTypes != PROJECTION_TYPE_NONE;
|
||||
if (mProjectionActive == projectionActive) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "Projection state changed: " + projectionActive);
|
||||
}
|
||||
mProjectionActive = projectionActive;
|
||||
if (mProjectionActive) {
|
||||
cancelIdlenessCheck();
|
||||
if (mIdle) {
|
||||
mIdle = false;
|
||||
mIdleListener.reportNewIdleState(mIdle);
|
||||
}
|
||||
} else {
|
||||
maybeScheduleIdlenessCheck("Projection ended");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,8 +136,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
pw.print(" mIdle: "); pw.println(mIdle);
|
||||
pw.print(" mScreenOn: "); pw.println(mScreenOn);
|
||||
pw.print(" mDockIdle: "); pw.println(mDockIdle);
|
||||
pw.print(" mInCarMode: ");
|
||||
pw.println(mInCarMode);
|
||||
pw.print(" mProjectionActive: "); pw.println(mProjectionActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,8 +154,9 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
StateControllerProto.IdleController.IdlenessTracker.DeviceIdlenessTracker.IS_DOCK_IDLE,
|
||||
mDockIdle);
|
||||
proto.write(
|
||||
StateControllerProto.IdleController.IdlenessTracker.DeviceIdlenessTracker.IN_CAR_MODE,
|
||||
mInCarMode);
|
||||
StateControllerProto.IdleController.IdlenessTracker.DeviceIdlenessTracker
|
||||
.PROJECTION_ACTIVE,
|
||||
mProjectionActive);
|
||||
|
||||
proto.end(diToken);
|
||||
proto.end(token);
|
||||
@@ -186,18 +212,6 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
}
|
||||
maybeScheduleIdlenessCheck(action);
|
||||
break;
|
||||
case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED:
|
||||
mInCarMode = true;
|
||||
cancelIdlenessCheck();
|
||||
if (mIdle) {
|
||||
mIdle = false;
|
||||
mIdleListener.reportNewIdleState(mIdle);
|
||||
}
|
||||
break;
|
||||
case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED:
|
||||
mInCarMode = false;
|
||||
maybeScheduleIdlenessCheck(action);
|
||||
break;
|
||||
case ActivityManagerService.ACTION_TRIGGER_IDLE:
|
||||
handleIdleTrigger();
|
||||
break;
|
||||
@@ -205,7 +219,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
}
|
||||
|
||||
private void maybeScheduleIdlenessCheck(String reason) {
|
||||
if ((!mScreenOn || mDockIdle) && !mInCarMode) {
|
||||
if ((!mScreenOn || mDockIdle) && !mProjectionActive) {
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
final long when = nowElapsed + mInactivityIdleThreshold;
|
||||
if (DEBUG) {
|
||||
@@ -222,7 +236,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
|
||||
private void handleIdleTrigger() {
|
||||
// idle time starts now. Do not set mIdle if screen is on.
|
||||
if (!mIdle && (!mScreenOn || mDockIdle) && !mInCarMode) {
|
||||
if (!mIdle && (!mScreenOn || mDockIdle) && !mProjectionActive) {
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
|
||||
}
|
||||
@@ -231,7 +245,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle="
|
||||
+ mIdle + " screen=" + mScreenOn + " car=" + mInCarMode);
|
||||
+ mIdle + " screen=" + mScreenOn + " projection=" + mProjectionActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ package android {
|
||||
field public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
|
||||
field public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
|
||||
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
|
||||
field public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE";
|
||||
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
|
||||
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
|
||||
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
|
||||
@@ -234,6 +235,7 @@ package android {
|
||||
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
|
||||
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
|
||||
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
|
||||
field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
|
||||
field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
|
||||
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
|
||||
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
|
||||
@@ -796,7 +798,11 @@ package android.app {
|
||||
}
|
||||
|
||||
public class UiModeManager {
|
||||
method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangeListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangeListener);
|
||||
method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int);
|
||||
method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public int getActiveProjectionTypes();
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public java.util.Set<java.lang.String> getProjectingPackages(int);
|
||||
method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangeListener(@NonNull android.app.UiModeManager.OnProjectionStateChangeListener);
|
||||
field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED";
|
||||
field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED";
|
||||
field public static final int DEFAULT_PRIORITY = 0; // 0x0
|
||||
@@ -804,6 +810,10 @@ package android.app {
|
||||
field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY";
|
||||
}
|
||||
|
||||
public static interface UiModeManager.OnProjectionStateChangeListener {
|
||||
method public void onProjectionStateChanged(int, @NonNull java.util.Set<java.lang.String>);
|
||||
}
|
||||
|
||||
public final class Vr2dDisplayProperties implements android.os.Parcelable {
|
||||
ctor public Vr2dDisplayProperties(int, int, int);
|
||||
method public int describeContents();
|
||||
|
||||
@@ -182,6 +182,7 @@ package android {
|
||||
field public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
|
||||
field public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
|
||||
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
|
||||
field public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE";
|
||||
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
|
||||
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
|
||||
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
|
||||
@@ -234,6 +235,7 @@ package android {
|
||||
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
|
||||
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
|
||||
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
|
||||
field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
|
||||
field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
|
||||
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
|
||||
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
|
||||
@@ -744,7 +746,11 @@ package android.app {
|
||||
}
|
||||
|
||||
public class UiModeManager {
|
||||
method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangeListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangeListener);
|
||||
method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int);
|
||||
method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public int getActiveProjectionTypes();
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public java.util.Set<java.lang.String> getProjectingPackages(int);
|
||||
method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangeListener(@NonNull android.app.UiModeManager.OnProjectionStateChangeListener);
|
||||
field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED";
|
||||
field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED";
|
||||
field public static final int DEFAULT_PRIORITY = 0; // 0x0
|
||||
@@ -752,6 +758,10 @@ package android.app {
|
||||
field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY";
|
||||
}
|
||||
|
||||
public static interface UiModeManager.OnProjectionStateChangeListener {
|
||||
method public void onProjectionStateChanged(int, @NonNull java.util.Set<java.lang.String>);
|
||||
}
|
||||
|
||||
public final class Vr2dDisplayProperties implements android.os.Parcelable {
|
||||
ctor public Vr2dDisplayProperties(int, int, int);
|
||||
method public int describeContents();
|
||||
|
||||
@@ -308,6 +308,11 @@ package android.app {
|
||||
public class UiModeManager {
|
||||
method public boolean isNightModeLocked();
|
||||
method public boolean isUiModeLocked();
|
||||
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
|
||||
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
|
||||
field public static final int PROJECTION_TYPE_ALL = 65535; // 0xffff
|
||||
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
|
||||
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
|
||||
}
|
||||
|
||||
public class WallpaperManager {
|
||||
|
||||
22
core/java/android/app/IOnProjectionStateChangeListener.aidl
Normal file
22
core/java/android/app/IOnProjectionStateChangeListener.aidl
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 android.app;
|
||||
|
||||
/** {@hide} */
|
||||
oneway interface IOnProjectionStateChangeListener {
|
||||
void onProjectionStateChanged(int activeProjectionTypes, in List<String> projectingPackages);
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.app.IOnProjectionStateChangeListener;
|
||||
|
||||
/**
|
||||
* Interface used to control special UI modes.
|
||||
* @hide
|
||||
@@ -93,4 +95,34 @@ interface IUiModeManager {
|
||||
* Sets custom end clock time
|
||||
*/
|
||||
void setCustomNightModeEnd(long time);
|
||||
|
||||
/**
|
||||
* Sets projection state for the caller for the given projection type.
|
||||
*/
|
||||
boolean requestProjection(in IBinder binder, int projectionType, String callingPackage);
|
||||
|
||||
/**
|
||||
* Releases projection state for the caller for the given projection type.
|
||||
*/
|
||||
boolean releaseProjection(int projectionType, String callingPackage);
|
||||
|
||||
/**
|
||||
* Registers a listener for changes to projection state.
|
||||
*/
|
||||
void addOnProjectionStateChangeListener(in IOnProjectionStateChangeListener listener, int projectionType);
|
||||
|
||||
/**
|
||||
* Unregisters a listener for changes to projection state.
|
||||
*/
|
||||
void removeOnProjectionStateChangeListener(in IOnProjectionStateChangeListener listener);
|
||||
|
||||
/**
|
||||
* Returns packages that have currently set the given projection type.
|
||||
*/
|
||||
List<String> getProjectingPackages(int projectionType);
|
||||
|
||||
/**
|
||||
* Returns currently set projection types.
|
||||
*/
|
||||
int getActiveProjectionTypes();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
@@ -27,20 +28,32 @@ import android.annotation.TestApi;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* This class provides access to the system uimode services. These services
|
||||
* allow applications to control UI modes of the device.
|
||||
* It provides functionality to disable the car mode and it gives access to the
|
||||
* night mode settings.
|
||||
*
|
||||
*
|
||||
* <p>These facilities are built on top of the underlying
|
||||
* {@link android.content.Intent#ACTION_DOCK_EVENT} broadcasts that are sent when the user
|
||||
* physical places the device into and out of a dock. When that happens,
|
||||
@@ -49,7 +62,7 @@ import java.time.LocalTime;
|
||||
* starts the corresponding mode activity if appropriate. See the
|
||||
* broadcasts {@link #ACTION_ENTER_CAR_MODE} and
|
||||
* {@link #ACTION_ENTER_DESK_MODE} for more information.
|
||||
*
|
||||
*
|
||||
* <p>In addition, the user may manually switch the system to car mode without
|
||||
* physically being in a dock. While in car mode -- whether by manual action
|
||||
* from the user or being physically placed in a dock -- a notification is
|
||||
@@ -59,6 +72,25 @@ import java.time.LocalTime;
|
||||
*/
|
||||
@SystemService(Context.UI_MODE_SERVICE)
|
||||
public class UiModeManager {
|
||||
/**
|
||||
* A listener with a single method that is invoked whenever the packages projecting using the
|
||||
* {@link ProjectionType}s for which it is registered change.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public interface OnProjectionStateChangeListener {
|
||||
/**
|
||||
* Callback invoked when projection state changes for a {@link ProjectionType} for which
|
||||
* this listener was added.
|
||||
* @param projectionType the listened-for {@link ProjectionType}s that have changed
|
||||
* @param packageNames the {@link Set} of package names that have currently set those
|
||||
* {@link ProjectionType}s.
|
||||
*/
|
||||
void onProjectionStateChanged(@ProjectionType int projectionType,
|
||||
@NonNull Set<String> packageNames);
|
||||
}
|
||||
|
||||
private static final String TAG = "UiModeManager";
|
||||
|
||||
/**
|
||||
@@ -100,7 +132,7 @@ public class UiModeManager {
|
||||
@SystemApi
|
||||
public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED =
|
||||
"android.app.action.ENTER_CAR_MODE_PRIORITIZED";
|
||||
|
||||
|
||||
/**
|
||||
* Broadcast sent when the device's UI has switch away from car mode back
|
||||
* to normal mode. Typically used by a car mode app, to dismiss itself
|
||||
@@ -137,7 +169,7 @@ public class UiModeManager {
|
||||
@SystemApi
|
||||
public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED =
|
||||
"android.app.action.EXIT_CAR_MODE_PRIORITIZED";
|
||||
|
||||
|
||||
/**
|
||||
* Broadcast sent when the device's UI has switched to desk mode,
|
||||
* by being placed in a desk dock. After
|
||||
@@ -151,7 +183,7 @@ public class UiModeManager {
|
||||
* of the broadcast to {@link Activity#RESULT_CANCELED}.
|
||||
*/
|
||||
public static String ACTION_ENTER_DESK_MODE = "android.app.action.ENTER_DESK_MODE";
|
||||
|
||||
|
||||
/**
|
||||
* Broadcast sent when the device's UI has switched away from desk mode back
|
||||
* to normal mode. Typically used by a desk mode app, to dismiss itself
|
||||
@@ -198,13 +230,13 @@ public class UiModeManager {
|
||||
* automatically switch night mode on and off based on the time.
|
||||
*/
|
||||
public static final int MODE_NIGHT_CUSTOM = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
|
||||
* never run in night mode.
|
||||
*/
|
||||
public static final int MODE_NIGHT_NO = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
|
||||
* always run in night mode.
|
||||
@@ -219,6 +251,24 @@ public class UiModeManager {
|
||||
*/
|
||||
private @Nullable Context mContext;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
/**
|
||||
* Map that stores internally created {@link InnerListener} objects keyed by their corresponding
|
||||
* externally provided {@link OnProjectionStateChangeListener} objects.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final Map<OnProjectionStateChangeListener, InnerListener>
|
||||
mProjectionStateListenerMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Resource manager that prevents memory leakage of Contexts via binder objects if clients
|
||||
* fail to remove listeners.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final OnProjectionStateChangeListenerResourceManager
|
||||
mOnProjectionStateChangeListenerResourceManager =
|
||||
new OnProjectionStateChangeListenerResourceManager();
|
||||
|
||||
@UnsupportedAppUsage
|
||||
/*package*/ UiModeManager() throws ServiceNotFoundException {
|
||||
this(null /* context */);
|
||||
@@ -251,7 +301,7 @@ public class UiModeManager {
|
||||
public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 0x0002;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "ENABLE_CAR_MODE_" }, value = {
|
||||
@IntDef(prefix = {"ENABLE_CAR_MODE_"}, value = {
|
||||
ENABLE_CAR_MODE_GO_CAR_HOME,
|
||||
ENABLE_CAR_MODE_ALLOW_SLEEP
|
||||
})
|
||||
@@ -362,7 +412,7 @@ public class UiModeManager {
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int DEFAULT_PRIORITY = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Turn off special mode if currently in car mode.
|
||||
* @param flags One of the disable car mode flags.
|
||||
@@ -595,4 +645,264 @@ public class UiModeManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates no projection type. Can be used to compare with the {@link ProjectionType} in
|
||||
* {@link OnProjectionStateChangeListener#onProjectionStateChanged(int, Set)}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public static final int PROJECTION_TYPE_NONE = 0x0000;
|
||||
/**
|
||||
* Automotive projection prevents degradation of GPS to save battery, routes incoming calls to
|
||||
* the automotive role holder, etc. For use with {@link #requestProjection(int)} and
|
||||
* {@link #clearProjectionState(int)}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public static final int PROJECTION_TYPE_AUTOMOTIVE = 0x0001;
|
||||
/**
|
||||
* Indicates all projection types. For use with
|
||||
* {@link #addOnProjectionStateChangeListener(int, Executor, OnProjectionStateChangeListener)}
|
||||
* and {@link #getProjectingPackages(int)}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public static final int PROJECTION_TYPE_ALL = 0xffff;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = {"PROJECTION_TYPE_"}, value = {
|
||||
PROJECTION_TYPE_NONE,
|
||||
PROJECTION_TYPE_AUTOMOTIVE,
|
||||
PROJECTION_TYPE_ALL,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ProjectionType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given {@link ProjectionType}.
|
||||
*
|
||||
* Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if
|
||||
* argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}.
|
||||
* @param projectionType the type of projection to request. This must be a single
|
||||
* {@link ProjectionType} and cannot be a bitmask.
|
||||
* @return true if the projection was successfully set
|
||||
* @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE},
|
||||
* {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
|
||||
conditional = true)
|
||||
public boolean requestProjection(@ProjectionType int projectionType) {
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.requestProjection(new Binder(), projectionType,
|
||||
mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the given {@link ProjectionType}.
|
||||
*
|
||||
* Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if
|
||||
* argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}.
|
||||
* @param projectionType the type of projection to release. This must be a single
|
||||
* {@link ProjectionType} and cannot be a bitmask.
|
||||
* @return true if the package had set projection and it was successfully released
|
||||
* @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE},
|
||||
* {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
|
||||
conditional = true)
|
||||
public boolean releaseProjection(@ProjectionType int projectionType) {
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.releaseProjection(projectionType, mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the packages that are currently projecting.
|
||||
*
|
||||
* @param projectionType the {@link ProjectionType}s to consider when computing which packages
|
||||
* are projecting. Use {@link #PROJECTION_TYPE_ALL} to get all projecting
|
||||
* packages.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
|
||||
@NonNull
|
||||
public Set<String> getProjectingPackages(@ProjectionType int projectionType) {
|
||||
if (mService != null) {
|
||||
try {
|
||||
return new ArraySet<>(mService.getProjectingPackages(projectionType));
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ProjectionType}s that are currently active.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
|
||||
public @ProjectionType int getActiveProjectionTypes() {
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.getActiveProjectionTypes();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
return PROJECTION_TYPE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the listener to receive callbacks when the packages projecting using the given
|
||||
* {@link ProjectionType}s change.
|
||||
*
|
||||
* @param projectionType one or more {@link ProjectionType}s to listen for changes regarding
|
||||
* @param executor an {@link Executor} on which to invoke the callbacks
|
||||
* @param listener the {@link OnProjectionStateChangeListener} to add
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
|
||||
public void addOnProjectionStateChangeListener(@ProjectionType int projectionType,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnProjectionStateChangeListener listener) {
|
||||
synchronized (mLock) {
|
||||
if (mProjectionStateListenerMap.containsKey(listener)) {
|
||||
Slog.i(TAG, "Attempted to add listener that was already added.");
|
||||
return;
|
||||
}
|
||||
if (mService != null) {
|
||||
InnerListener innerListener = new InnerListener(executor, listener,
|
||||
mOnProjectionStateChangeListenerResourceManager);
|
||||
try {
|
||||
mService.addOnProjectionStateChangeListener(innerListener, projectionType);
|
||||
mProjectionStateListenerMap.put(listener, innerListener);
|
||||
} catch (RemoteException e) {
|
||||
mOnProjectionStateChangeListenerResourceManager.remove(innerListener);
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener so it stops receiving updates for all {@link ProjectionType}s.
|
||||
*
|
||||
* @param listener the {@link OnProjectionStateChangeListener} to remove
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
|
||||
public void removeOnProjectionStateChangeListener(
|
||||
@NonNull OnProjectionStateChangeListener listener) {
|
||||
synchronized (mLock) {
|
||||
InnerListener innerListener = mProjectionStateListenerMap.get(listener);
|
||||
if (innerListener == null) {
|
||||
Slog.i(TAG, "Attempted to remove listener that was not added.");
|
||||
return;
|
||||
}
|
||||
if (mService != null) {
|
||||
try {
|
||||
mService.removeOnProjectionStateChangeListener(innerListener);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
mProjectionStateListenerMap.remove(listener);
|
||||
mOnProjectionStateChangeListenerResourceManager.remove(innerListener);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InnerListener extends IOnProjectionStateChangeListener.Stub {
|
||||
private final WeakReference<OnProjectionStateChangeListenerResourceManager>
|
||||
mResourceManager;
|
||||
|
||||
private InnerListener(@NonNull Executor executor,
|
||||
@NonNull OnProjectionStateChangeListener outerListener,
|
||||
@NonNull OnProjectionStateChangeListenerResourceManager resourceManager) {
|
||||
resourceManager.put(this, executor, outerListener);
|
||||
mResourceManager = new WeakReference<>(resourceManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectionStateChanged(int activeProjectionTypes,
|
||||
List<String> projectingPackages) {
|
||||
OnProjectionStateChangeListenerResourceManager resourceManager = mResourceManager.get();
|
||||
if (resourceManager == null) {
|
||||
Slog.w(TAG, "Can't execute onProjectionStateChanged, resource manager is gone.");
|
||||
return;
|
||||
}
|
||||
|
||||
OnProjectionStateChangeListener outerListener = resourceManager.getOuterListener(this);
|
||||
Executor executor = resourceManager.getExecutor(this);
|
||||
if (outerListener == null || executor == null) {
|
||||
Slog.w(TAG, "Can't execute onProjectionStatechanged, references are null.");
|
||||
return;
|
||||
}
|
||||
|
||||
executor.execute(PooledLambda.obtainRunnable(
|
||||
OnProjectionStateChangeListener::onProjectionStateChanged,
|
||||
outerListener,
|
||||
activeProjectionTypes,
|
||||
new ArraySet<>(projectingPackages)).recycleOnUse());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class that ensures we don't leak {@link Activity} or other large {@link Context} in
|
||||
* which this {@link UiModeManager} resides if/when it ends without unregistering associated
|
||||
* {@link OnProjectionStateChangeListener}s.
|
||||
*/
|
||||
private static class OnProjectionStateChangeListenerResourceManager {
|
||||
private final Map<InnerListener, OnProjectionStateChangeListener> mOuterListenerMap =
|
||||
new ArrayMap<>(1);
|
||||
private final Map<InnerListener, Executor> mExecutorMap = new ArrayMap<>(1);
|
||||
|
||||
void put(@NonNull InnerListener innerListener, @NonNull Executor executor,
|
||||
OnProjectionStateChangeListener outerListener) {
|
||||
mOuterListenerMap.put(innerListener, outerListener);
|
||||
mExecutorMap.put(innerListener, executor);
|
||||
}
|
||||
|
||||
void remove(InnerListener innerListener) {
|
||||
mOuterListenerMap.remove(innerListener);
|
||||
mExecutorMap.remove(innerListener);
|
||||
}
|
||||
|
||||
OnProjectionStateChangeListener getOuterListener(@NonNull InnerListener innerListener) {
|
||||
return mOuterListenerMap.get(innerListener);
|
||||
}
|
||||
|
||||
Executor getExecutor(@NonNull InnerListener innerListener) {
|
||||
return mExecutorMap.get(innerListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,7 +527,8 @@ message StateControllerProto {
|
||||
optional bool is_idle = 1;
|
||||
optional bool is_screen_on = 2;
|
||||
optional bool is_dock_idle = 3;
|
||||
optional bool in_car_mode = 4;
|
||||
reserved 4; // in_car_mode
|
||||
optional bool projection_active = 5;
|
||||
}
|
||||
|
||||
oneof active_tracker {
|
||||
|
||||
@@ -2674,6 +2674,21 @@
|
||||
<permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- Allows an app to read and listen to projection state.
|
||||
@hide
|
||||
@SystemApi
|
||||
-->
|
||||
<permission android:name="android.permission.READ_PROJECTION_STATE"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- Allows an app to set and release automotive projection.
|
||||
<p>Once permissions can be granted via role-only, this needs to be changed to
|
||||
protectionLevel="role" and added to the SYSTEM_AUTOMOTIVE_PROJECTION role.
|
||||
@hide
|
||||
@SystemApi
|
||||
-->
|
||||
<permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- ================================== -->
|
||||
<!-- Permissions affecting the system wallpaper -->
|
||||
|
||||
@@ -164,7 +164,6 @@ applications that come with the platform
|
||||
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
|
||||
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
|
||||
<permission name="android.permission.DUMP"/>
|
||||
<permission name="android.permission.HANDLE_CAR_MODE_CHANGES"/>
|
||||
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
|
||||
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
|
||||
<permission name="android.permission.MANAGE_USERS"/>
|
||||
@@ -175,6 +174,7 @@ applications that come with the platform
|
||||
<permission name="android.permission.READ_CARRIER_APP_INFO"/>
|
||||
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
|
||||
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
|
||||
<permission name="android.permission.READ_PROJECTION_STATE"/>
|
||||
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
|
||||
<permission name="android.permission.REBOOT"/>
|
||||
<permission name="android.permission.REGISTER_CALL_PROVIDER"/>
|
||||
@@ -376,6 +376,8 @@ applications that come with the platform
|
||||
<permission name="android.permission.STOP_APP_SWITCHES"/>
|
||||
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
|
||||
<permission name="android.permission.SUSPEND_APPS" />
|
||||
<!-- Permissions required for UiModeManager and Telecom car mode CTS tests -->
|
||||
<permission name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
|
||||
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
|
||||
<permission name="android.permission.USE_RESERVED_DISK"/>
|
||||
<permission name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"/>
|
||||
|
||||
@@ -250,6 +250,9 @@
|
||||
<!-- Permission required for CTS test - UiModeManagerTest -->
|
||||
<uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
|
||||
|
||||
<!-- Permission required for CTS tests - UiModeManagerTest, CarModeInCallServiceTest -->
|
||||
<uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"/>
|
||||
|
||||
<!-- Permission required for CTS test - SystemConfigTest -->
|
||||
<uses-permission android:name="android.permission.READ_CARRIER_APP_INFO"/>
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import static android.app.UiModeManager.DEFAULT_PRIORITY;
|
||||
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
|
||||
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
|
||||
import static android.app.UiModeManager.MODE_NIGHT_YES;
|
||||
import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
|
||||
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
|
||||
import static android.os.UserHandle.USER_SYSTEM;
|
||||
import static android.util.TimeUtils.isTimeBetween;
|
||||
|
||||
@@ -30,6 +32,7 @@ import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IOnProjectionStateChangeListener;
|
||||
import android.app.IUiModeManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@@ -48,10 +51,12 @@ import android.net.Uri;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.ServiceType;
|
||||
import android.os.PowerManagerInternal;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ServiceManager;
|
||||
@@ -65,8 +70,10 @@ import android.service.vr.IVrManager;
|
||||
import android.service.vr.IVrStateCallbacks;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.DisableCarModeActivity;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
@@ -83,7 +90,9 @@ import java.time.DateTimeException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -97,7 +106,9 @@ final class UiModeManagerService extends SystemService {
|
||||
private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
|
||||
private static final String SYSTEM_PROPERTY_DEVICE_THEME = "persist.sys.theme";
|
||||
|
||||
final Object mLock = new Object();
|
||||
private final Injector mInjector;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
|
||||
|
||||
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
|
||||
@@ -162,17 +173,25 @@ final class UiModeManagerService extends SystemService {
|
||||
private final LocalService mLocalService = new LocalService();
|
||||
private PowerManagerInternal mLocalPowerManager;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private SparseArray<List<ProjectionHolder>> mProjectionHolders;
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private SparseArray<RemoteCallbackList<IOnProjectionStateChangeListener>> mProjectionListeners;
|
||||
|
||||
public UiModeManagerService(Context context) {
|
||||
super(context);
|
||||
mConfiguration.setToDefaults();
|
||||
this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected UiModeManagerService(Context context, boolean setupWizardComplete,
|
||||
TwilightManager tm) {
|
||||
this(context);
|
||||
TwilightManager tm, Injector injector) {
|
||||
super(context);
|
||||
mConfiguration.setToDefaults();
|
||||
mSetupWizardComplete = setupWizardComplete;
|
||||
mTwilightManager = tm;
|
||||
mInjector = injector;
|
||||
}
|
||||
|
||||
private static Intent buildHomeIntent(String category) {
|
||||
@@ -639,7 +658,7 @@ final class UiModeManagerService extends SystemService {
|
||||
// If the caller is the system, we will allow the DISABLE_CAR_MODE_ALL_PRIORITIES car
|
||||
// mode flag to be specified; this is so that the user can disable car mode at all
|
||||
// priorities using the persistent notification.
|
||||
boolean isSystemCaller = Binder.getCallingUid() == Process.SYSTEM_UID;
|
||||
boolean isSystemCaller = mInjector.getCallingUid() == Process.SYSTEM_UID;
|
||||
final int carModeFlags =
|
||||
isSystemCaller ? flags : flags & ~UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES;
|
||||
|
||||
@@ -853,8 +872,308 @@ final class UiModeManagerService extends SystemService {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestProjection(IBinder binder,
|
||||
@UiModeManager.ProjectionType int projectionType,
|
||||
@NonNull String callingPackage) {
|
||||
assertLegit(callingPackage);
|
||||
assertSingleProjectionType(projectionType);
|
||||
enforceProjectionTypePermissions(projectionType);
|
||||
synchronized (mLock) {
|
||||
if (mProjectionHolders == null) {
|
||||
mProjectionHolders = new SparseArray<>(1);
|
||||
}
|
||||
if (!mProjectionHolders.contains(projectionType)) {
|
||||
mProjectionHolders.put(projectionType, new ArrayList<>(1));
|
||||
}
|
||||
List<ProjectionHolder> currentHolders = mProjectionHolders.get(projectionType);
|
||||
|
||||
// For all projection types, it's a noop if already held.
|
||||
for (int i = 0; i < currentHolders.size(); ++i) {
|
||||
if (callingPackage.equals(currentHolders.get(i).mPackageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce projection type-specific restrictions here.
|
||||
|
||||
// Automotive projection can only be set if it is currently unset. The case where it
|
||||
// is already set by the calling package is taken care of above.
|
||||
if (projectionType == PROJECTION_TYPE_AUTOMOTIVE && !currentHolders.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectionHolder projectionHolder = new ProjectionHolder(callingPackage,
|
||||
projectionType, binder,
|
||||
UiModeManagerService.this::releaseProjectionUnchecked);
|
||||
if (!projectionHolder.linkToDeath()) {
|
||||
return false;
|
||||
}
|
||||
currentHolders.add(projectionHolder);
|
||||
Slog.d(TAG, "Package " + callingPackage + " set projection type "
|
||||
+ projectionType + ".");
|
||||
onProjectionStateChangedLocked(projectionType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseProjection(@UiModeManager.ProjectionType int projectionType,
|
||||
@NonNull String callingPackage) {
|
||||
assertLegit(callingPackage);
|
||||
assertSingleProjectionType(projectionType);
|
||||
enforceProjectionTypePermissions(projectionType);
|
||||
return releaseProjectionUnchecked(projectionType, callingPackage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @UiModeManager.ProjectionType int getActiveProjectionTypes() {
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.READ_PROJECTION_STATE, "getActiveProjectionTypes");
|
||||
@UiModeManager.ProjectionType int projectionTypeFlag = PROJECTION_TYPE_NONE;
|
||||
synchronized (mLock) {
|
||||
if (mProjectionHolders != null) {
|
||||
for (int i = 0; i < mProjectionHolders.size(); ++i) {
|
||||
if (!mProjectionHolders.valueAt(i).isEmpty()) {
|
||||
projectionTypeFlag = projectionTypeFlag | mProjectionHolders.keyAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return projectionTypeFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getProjectingPackages(
|
||||
@UiModeManager.ProjectionType int projectionType) {
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.READ_PROJECTION_STATE, "getProjectionState");
|
||||
synchronized (mLock) {
|
||||
List<String> packageNames = new ArrayList<>();
|
||||
populateWithRelevantActivePackageNames(projectionType, packageNames);
|
||||
return packageNames;
|
||||
}
|
||||
}
|
||||
|
||||
public void addOnProjectionStateChangeListener(IOnProjectionStateChangeListener listener,
|
||||
@UiModeManager.ProjectionType int projectionType) {
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.READ_PROJECTION_STATE,
|
||||
"registerProjectionStateListener");
|
||||
if (projectionType == PROJECTION_TYPE_NONE) {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (mProjectionListeners == null) {
|
||||
mProjectionListeners = new SparseArray<>(1);
|
||||
}
|
||||
if (!mProjectionListeners.contains(projectionType)) {
|
||||
mProjectionListeners.put(projectionType, new RemoteCallbackList<>());
|
||||
}
|
||||
if (mProjectionListeners.get(projectionType).register(listener)) {
|
||||
// If any of those types are active, send a callback immediately.
|
||||
List<String> packageNames = new ArrayList<>();
|
||||
@UiModeManager.ProjectionType int activeProjectionTypes =
|
||||
populateWithRelevantActivePackageNames(projectionType, packageNames);
|
||||
if (!packageNames.isEmpty()) {
|
||||
try {
|
||||
listener.onProjectionStateChanged(activeProjectionTypes, packageNames);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG,
|
||||
"Failed a call to onProjectionStateChanged() during listener "
|
||||
+ "registration.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void removeOnProjectionStateChangeListener(
|
||||
IOnProjectionStateChangeListener listener) {
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.READ_PROJECTION_STATE,
|
||||
"unregisterProjectionStateListener");
|
||||
synchronized (mLock) {
|
||||
if (mProjectionListeners != null) {
|
||||
for (int i = 0; i < mProjectionListeners.size(); ++i) {
|
||||
mProjectionListeners.valueAt(i).unregister(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
|
||||
if ((p & PROJECTION_TYPE_AUTOMOTIVE) != 0) {
|
||||
getContext().enforceCallingPermission(
|
||||
android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
|
||||
"toggleProjection");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertSingleProjectionType(@UiModeManager.ProjectionType int p) {
|
||||
// To be a single projection type it must be greater than zero and an exact power of two.
|
||||
boolean projectionTypeIsPowerOfTwoOrZero = (p & p - 1) == 0;
|
||||
if (p <= 0 || !projectionTypeIsPowerOfTwoOrZero) {
|
||||
throw new IllegalArgumentException("Must specify exactly one projection type.");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> toPackageNameList(Collection<ProjectionHolder> c) {
|
||||
List<String> packageNames = new ArrayList<>();
|
||||
for (ProjectionHolder p : c) {
|
||||
packageNames.add(p.mPackageName);
|
||||
}
|
||||
return packageNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates a list with the package names that have set any of the given projection types.
|
||||
* @param projectionType the projection types to include
|
||||
* @param packageNames the list to populate with package names
|
||||
* @return the active projection types
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@UiModeManager.ProjectionType
|
||||
private int populateWithRelevantActivePackageNames(
|
||||
@UiModeManager.ProjectionType int projectionType, List<String> packageNames) {
|
||||
packageNames.clear();
|
||||
@UiModeManager.ProjectionType int projectionTypeFlag = PROJECTION_TYPE_NONE;
|
||||
if (mProjectionHolders != null) {
|
||||
for (int i = 0; i < mProjectionHolders.size(); ++i) {
|
||||
int key = mProjectionHolders.keyAt(i);
|
||||
List<ProjectionHolder> holders = mProjectionHolders.valueAt(i);
|
||||
if ((projectionType & key) != 0) {
|
||||
if (packageNames.addAll(toPackageNameList(holders))) {
|
||||
projectionTypeFlag = projectionTypeFlag | key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return projectionTypeFlag;
|
||||
}
|
||||
|
||||
private boolean releaseProjectionUnchecked(@UiModeManager.ProjectionType int projectionType,
|
||||
@NonNull String pkg) {
|
||||
synchronized (mLock) {
|
||||
boolean removed = false;
|
||||
if (mProjectionHolders != null) {
|
||||
List<ProjectionHolder> holders = mProjectionHolders.get(projectionType);
|
||||
if (holders != null) {
|
||||
// Iterate backward so we can safely remove while iterating.
|
||||
for (int i = holders.size() - 1; i >= 0; --i) {
|
||||
ProjectionHolder holder = holders.get(i);
|
||||
if (pkg.equals(holder.mPackageName)) {
|
||||
holder.unlinkToDeath();
|
||||
Slog.d(TAG, "Projection type " + projectionType + " released by "
|
||||
+ pkg + ".");
|
||||
holders.remove(i);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed) {
|
||||
onProjectionStateChangedLocked(projectionType);
|
||||
} else {
|
||||
Slog.w(TAG, pkg + " tried to release projection type " + projectionType
|
||||
+ " but was not set by that package.");
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProjectionHolder implements IBinder.DeathRecipient {
|
||||
private final String mPackageName;
|
||||
private final @UiModeManager.ProjectionType int mProjectionType;
|
||||
private final IBinder mBinder;
|
||||
private final ProjectionReleaser mProjectionReleaser;
|
||||
|
||||
private ProjectionHolder(String packageName,
|
||||
@UiModeManager.ProjectionType int projectionType, IBinder binder,
|
||||
ProjectionReleaser projectionReleaser) {
|
||||
mPackageName = packageName;
|
||||
mProjectionType = projectionType;
|
||||
mBinder = binder;
|
||||
mProjectionReleaser = projectionReleaser;
|
||||
}
|
||||
|
||||
private boolean linkToDeath() {
|
||||
try {
|
||||
mBinder.linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "linkToDeath failed for projection requester: " + mPackageName + ".",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void unlinkToDeath() {
|
||||
mBinder.unlinkToDeath(this, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Slog.w(TAG, "Projection holder " + mPackageName
|
||||
+ " died. Releasing projection type " + mProjectionType + ".");
|
||||
mProjectionReleaser.release(mProjectionType, mPackageName);
|
||||
}
|
||||
|
||||
private interface ProjectionReleaser {
|
||||
boolean release(@UiModeManager.ProjectionType int projectionType,
|
||||
@NonNull String packageName);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLegit(@NonNull String packageName) {
|
||||
if (!doesPackageHaveCallingUid(packageName)) {
|
||||
throw new SecurityException("Caller claimed bogus packageName: " + packageName + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doesPackageHaveCallingUid(@NonNull String packageName) {
|
||||
try {
|
||||
return getContext().getPackageManager().getPackageUid(packageName, 0)
|
||||
== mInjector.getCallingUid();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void onProjectionStateChangedLocked(
|
||||
@UiModeManager.ProjectionType int changedProjectionType) {
|
||||
if (mProjectionListeners == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < mProjectionListeners.size(); ++i) {
|
||||
int listenerProjectionType = mProjectionListeners.keyAt(i);
|
||||
// Every listener that is affected must be called back with all the state they are
|
||||
// listening for.
|
||||
if ((changedProjectionType & listenerProjectionType) != 0) {
|
||||
RemoteCallbackList<IOnProjectionStateChangeListener> listeners =
|
||||
mProjectionListeners.valueAt(i);
|
||||
List<String> packageNames = new ArrayList<>();
|
||||
@UiModeManager.ProjectionType int activeProjectionTypes =
|
||||
populateWithRelevantActivePackageNames(listenerProjectionType,
|
||||
packageNames);
|
||||
int listenerCount = listeners.beginBroadcast();
|
||||
for (int j = 0; j < listenerCount; ++j) {
|
||||
try {
|
||||
listeners.getBroadcastItem(j).onProjectionStateChanged(
|
||||
activeProjectionTypes, packageNames);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed a call to onProjectionStateChanged().");
|
||||
}
|
||||
}
|
||||
listeners.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onCustomTimeUpdated(int user) {
|
||||
persistNightMode(user);
|
||||
if (mNightMode != MODE_NIGHT_CUSTOM) return;
|
||||
@@ -1656,4 +1975,11 @@ final class UiModeManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static class Injector {
|
||||
public int getCallingUid() {
|
||||
return Binder.getCallingUid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,8 @@ package com.android.server.power.batterysaver;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.BatterySaverPolicyConfig;
|
||||
@@ -190,12 +186,12 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
/**
|
||||
* Whether accessibility is currently enabled or not.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private boolean mAccessibilityEnabled;
|
||||
@VisibleForTesting
|
||||
final PolicyBoolean mAccessibilityEnabled = new PolicyBoolean("accessibility");
|
||||
|
||||
/** Whether the phone is projecting in car mode or not. */
|
||||
@GuardedBy("mLock")
|
||||
private boolean mCarModeEnabled;
|
||||
/** Whether the phone has set automotive projection or not. */
|
||||
@VisibleForTesting
|
||||
final PolicyBoolean mAutomotiveProjectionActive = new PolicyBoolean("automotiveProjection");
|
||||
|
||||
/** The current default adaptive policy. */
|
||||
@GuardedBy("mLock")
|
||||
@@ -235,19 +231,8 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
private final ContentResolver mContentResolver;
|
||||
private final BatterySavingStats mBatterySavingStats;
|
||||
|
||||
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED:
|
||||
setCarModeEnabled(true);
|
||||
break;
|
||||
case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED:
|
||||
setCarModeEnabled(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
private final UiModeManager.OnProjectionStateChangeListener mOnProjectionStateChangeListener =
|
||||
(t, pkgs) -> mAutomotiveProjectionActive.update(!pkgs.isEmpty());
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();
|
||||
@@ -282,24 +267,14 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
|
||||
final AccessibilityManager acm = mContext.getSystemService(AccessibilityManager.class);
|
||||
|
||||
acm.addAccessibilityStateChangeListener((enabled) -> setAccessibilityEnabled(enabled));
|
||||
final boolean accessibilityEnabled = acm.isEnabled();
|
||||
synchronized (mLock) {
|
||||
mAccessibilityEnabled = accessibilityEnabled;
|
||||
}
|
||||
acm.addAccessibilityStateChangeListener(enabled -> mAccessibilityEnabled.update(enabled));
|
||||
mAccessibilityEnabled.initialize(acm.isEnabled());
|
||||
|
||||
final IntentFilter filter = new IntentFilter(
|
||||
UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
|
||||
filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
|
||||
// The ENTER/EXIT_CAR_MODE_PRIORITIZED intents are sent to UserHandle.ALL, so no need to
|
||||
// register as all users here.
|
||||
mContext.registerReceiver(mBroadcastReceiver, filter);
|
||||
final boolean carModeEnabled =
|
||||
mContext.getSystemService(UiModeManager.class).getCurrentModeType()
|
||||
== Configuration.UI_MODE_TYPE_CAR;
|
||||
synchronized (mLock) {
|
||||
mCarModeEnabled = carModeEnabled;
|
||||
}
|
||||
UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
|
||||
uiModeManager.addOnProjectionStateChangeListener(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE,
|
||||
mContext.getMainExecutor(), mOnProjectionStateChangeListener);
|
||||
mAutomotiveProjectionActive.initialize(
|
||||
uiModeManager.getActiveProjectionTypes() != UiModeManager.PROJECTION_TYPE_NONE);
|
||||
|
||||
onChange(true, null);
|
||||
}
|
||||
@@ -450,7 +425,7 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
final int locationMode;
|
||||
|
||||
invalidatePowerSaveModeCaches();
|
||||
if (mCarModeEnabled
|
||||
if (mAutomotiveProjectionActive.get()
|
||||
&& rawPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE
|
||||
&& rawPolicy.locationMode != PowerManager.LOCATION_MODE_FOREGROUND_ONLY) {
|
||||
// If car projection is enabled, ensure that navigation works.
|
||||
@@ -470,12 +445,12 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
rawPolicy.disableOptionalSensors,
|
||||
rawPolicy.disableSoundTrigger,
|
||||
// Don't disable vibration when accessibility is on.
|
||||
rawPolicy.disableVibration && !mAccessibilityEnabled,
|
||||
rawPolicy.disableVibration && !mAccessibilityEnabled.get(),
|
||||
rawPolicy.enableAdjustBrightness,
|
||||
rawPolicy.enableDataSaver,
|
||||
rawPolicy.enableFirewall,
|
||||
// Don't force night mode when car projection is enabled.
|
||||
rawPolicy.enableNightMode && !mCarModeEnabled,
|
||||
rawPolicy.enableNightMode && !mAutomotiveProjectionActive.get(),
|
||||
rawPolicy.enableQuickDoze,
|
||||
rawPolicy.filesForInteractive,
|
||||
rawPolicy.filesForNoninteractive,
|
||||
@@ -1073,8 +1048,8 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
+ Settings.Global.BATTERY_SAVER_ADAPTIVE_DEVICE_SPECIFIC_CONSTANTS);
|
||||
pw.println(" value: " + mAdaptiveDeviceSpecificSettings);
|
||||
|
||||
pw.println(" mAccessibilityEnabled=" + mAccessibilityEnabled);
|
||||
pw.println(" mCarModeEnabled=" + mCarModeEnabled);
|
||||
pw.println(" mAccessibilityEnabled=" + mAccessibilityEnabled.get());
|
||||
pw.println(" mAutomotiveProjectionActive=" + mAutomotiveProjectionActive.get());
|
||||
pw.println(" mPolicyLevel=" + mPolicyLevel);
|
||||
|
||||
dumpPolicyLocked(pw, " ", "full", mFullPolicy);
|
||||
@@ -1147,24 +1122,42 @@ public class BatterySaverPolicy extends ContentObserver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A boolean value which should trigger a policy update when it changes.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void setAccessibilityEnabled(boolean enabled) {
|
||||
synchronized (mLock) {
|
||||
if (mAccessibilityEnabled != enabled) {
|
||||
mAccessibilityEnabled = enabled;
|
||||
updatePolicyDependenciesLocked();
|
||||
maybeNotifyListenersOfPolicyChange();
|
||||
class PolicyBoolean {
|
||||
private final String mDebugName;
|
||||
@GuardedBy("mLock")
|
||||
private boolean mValue;
|
||||
|
||||
private PolicyBoolean(String debugName) {
|
||||
mDebugName = debugName;
|
||||
}
|
||||
|
||||
/** Sets the initial value without triggering a policy update. */
|
||||
private void initialize(boolean initialValue) {
|
||||
synchronized (mLock) {
|
||||
mValue = initialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setCarModeEnabled(boolean enabled) {
|
||||
synchronized (mLock) {
|
||||
if (mCarModeEnabled != enabled) {
|
||||
mCarModeEnabled = enabled;
|
||||
updatePolicyDependenciesLocked();
|
||||
maybeNotifyListenersOfPolicyChange();
|
||||
private boolean get() {
|
||||
synchronized (mLock) {
|
||||
return mValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets a value, which if different from the current value, triggers a policy update. */
|
||||
@VisibleForTesting
|
||||
void update(boolean newValue) {
|
||||
synchronized (mLock) {
|
||||
if (mValue != newValue) {
|
||||
Slog.d(TAG, mDebugName + " changed to " + newValue + ", updating policy.");
|
||||
mValue = newValue;
|
||||
updatePolicyDependenciesLocked();
|
||||
maybeNotifyListenersOfPolicyChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.UiModeManager;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
@@ -147,6 +148,8 @@ public class JobSchedulerServiceTest {
|
||||
JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
|
||||
JobSchedulerService.sElapsedRealtimeClock =
|
||||
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
|
||||
// Called by DeviceIdlenessTracker
|
||||
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
|
||||
|
||||
mService = new TestJobSchedulerService(mContext);
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON"/>
|
||||
<uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
|
||||
<uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
|
||||
|
||||
<!-- Uses API introduced in O (26) -->
|
||||
<uses-sdk android:minSdkVersion="1"
|
||||
|
||||
@@ -117,7 +117,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
|
||||
@SmallTest
|
||||
public void testGetBatterySaverPolicy_PolicyVibration_WithAccessibilityEnabled() {
|
||||
mBatterySaverPolicy.setAccessibilityEnabled(true);
|
||||
mBatterySaverPolicy.mAccessibilityEnabled.update(true);
|
||||
testServiceDefaultValue_Off(ServiceType.VIBRATION);
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
verifyBatterySaverConstantsUpdated();
|
||||
}
|
||||
|
||||
public void testCarModeChanges_Full() {
|
||||
public void testAutomotiveProjectionChanges_Full() {
|
||||
mBatterySaverPolicy.updateConstantsLocked(
|
||||
"gps_mode=" + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
|
||||
+ ",enable_night_mode=true", "");
|
||||
@@ -350,7 +350,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
assertTrue(mBatterySaverPolicy.getBatterySaverPolicy(
|
||||
ServiceType.NIGHT_MODE).batterySaverEnabled);
|
||||
|
||||
mBatterySaverPolicy.setCarModeEnabled(true);
|
||||
mBatterySaverPolicy.mAutomotiveProjectionActive.update(true);
|
||||
|
||||
assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
|
||||
.isAnyOf(PowerManager.LOCATION_MODE_NO_CHANGE,
|
||||
@@ -358,7 +358,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
assertFalse(mBatterySaverPolicy.getBatterySaverPolicy(
|
||||
ServiceType.NIGHT_MODE).batterySaverEnabled);
|
||||
|
||||
mBatterySaverPolicy.setCarModeEnabled(false);
|
||||
mBatterySaverPolicy.mAutomotiveProjectionActive.update(false);
|
||||
|
||||
assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
|
||||
.isEqualTo(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
|
||||
@@ -366,7 +366,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
ServiceType.NIGHT_MODE).batterySaverEnabled);
|
||||
}
|
||||
|
||||
public void testCarModeChanges_Adaptive() {
|
||||
public void testAutomotiveProjectionChanges_Adaptive() {
|
||||
mBatterySaverPolicy.setAdaptivePolicyLocked(
|
||||
Policy.fromSettings(
|
||||
"gps_mode=" + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
|
||||
@@ -377,7 +377,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
assertTrue(mBatterySaverPolicy.getBatterySaverPolicy(
|
||||
ServiceType.NIGHT_MODE).batterySaverEnabled);
|
||||
|
||||
mBatterySaverPolicy.setCarModeEnabled(true);
|
||||
mBatterySaverPolicy.mAutomotiveProjectionActive.update(true);
|
||||
|
||||
assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
|
||||
.isAnyOf(PowerManager.LOCATION_MODE_NO_CHANGE,
|
||||
@@ -385,7 +385,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
|
||||
assertFalse(mBatterySaverPolicy.getBatterySaverPolicy(
|
||||
ServiceType.NIGHT_MODE).batterySaverEnabled);
|
||||
|
||||
mBatterySaverPolicy.setCarModeEnabled(false);
|
||||
mBatterySaverPolicy.mAutomotiveProjectionActive.update(false);
|
||||
|
||||
assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
|
||||
.isEqualTo(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
|
||||
|
||||
@@ -25,6 +25,9 @@ android_test {
|
||||
"hamcrest-library",
|
||||
"testables",
|
||||
"truth-prebuilt",
|
||||
// TODO: remove once Android migrates to JUnit 4.12,
|
||||
// which provides assertThrows
|
||||
"testng",
|
||||
],
|
||||
|
||||
libs: [
|
||||
|
||||
@@ -16,48 +16,21 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IUiModeManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManagerInternal;
|
||||
import android.os.PowerSaveState;
|
||||
import android.os.RemoteException;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import com.android.server.twilight.TwilightListener;
|
||||
import com.android.server.twilight.TwilightManager;
|
||||
import com.android.server.twilight.TwilightState;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
|
||||
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
|
||||
import static android.app.UiModeManager.MODE_NIGHT_NO;
|
||||
import static android.app.UiModeManager.MODE_NIGHT_YES;
|
||||
import static android.app.UiModeManager.PROJECTION_TYPE_ALL;
|
||||
import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
|
||||
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
|
||||
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
@@ -69,15 +42,60 @@ import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IOnProjectionStateChangeListener;
|
||||
import android.app.IUiModeManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManagerInternal;
|
||||
import android.os.PowerSaveState;
|
||||
import android.os.RemoteException;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import com.android.server.twilight.TwilightListener;
|
||||
import com.android.server.twilight.TwilightManager;
|
||||
import com.android.server.twilight.TwilightState;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class UiModeManagerServiceTest extends UiServiceTestCase {
|
||||
private static final String PACKAGE_NAME = "Diane Coffee";
|
||||
private UiModeManagerService mUiManagerService;
|
||||
private IUiModeManager mService;
|
||||
@Mock
|
||||
@@ -100,6 +118,10 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
|
||||
private TwilightState mTwilightState;
|
||||
@Mock
|
||||
PowerManagerInternal mLocalPowerManager;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private IBinder mBinder;
|
||||
|
||||
private BroadcastReceiver mScreenOffCallback;
|
||||
private BroadcastReceiver mTimeChangedCallback;
|
||||
@@ -124,6 +146,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
|
||||
.thenReturn(new PowerSaveState.Builder().setBatterySaverEnabled(false).build());
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||
when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
|
||||
when(mTwilightManager.getLastTwilightState()).thenReturn(mTwilightState);
|
||||
@@ -156,8 +179,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
|
||||
addLocalService(PowerManagerInternal.class, mLocalPowerManager);
|
||||
addLocalService(TwilightManager.class, mTwilightManager);
|
||||
|
||||
mUiManagerService = new UiModeManagerService(mContext, true,
|
||||
mTwilightManager);
|
||||
mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
|
||||
mTwilightManager, new TestInjector());
|
||||
try {
|
||||
mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
|
||||
} catch (SecurityException e) {/* ignore for permission denial */}
|
||||
@@ -449,4 +472,353 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
|
||||
return (mUiManagerService.getConfiguration().uiMode
|
||||
& Configuration.UI_MODE_NIGHT_YES) != 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_failsForBogusPackageName() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
|
||||
.thenReturn(TestInjector.CALLING_UID + 1);
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
|
||||
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_failsIfNameNotFound() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
|
||||
.thenThrow(new PackageManager.NameNotFoundException());
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
|
||||
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_failsIfNoProjectionTypes() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME));
|
||||
verify(mContext, never()).enforceCallingPermission(
|
||||
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
|
||||
verifyZeroInteractions(mBinder);
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_failsIfMultipleProjectionTypes() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
|
||||
// Don't use PROJECTION_TYPE_ALL because that's actually == -1 and will fail the > 0 check.
|
||||
int multipleProjectionTypes = PROJECTION_TYPE_AUTOMOTIVE | 0x0002 | 0x0004;
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> mService.requestProjection(mBinder, multipleProjectionTypes, PACKAGE_NAME));
|
||||
verify(mContext, never()).enforceCallingPermission(
|
||||
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
|
||||
verifyZeroInteractions(mBinder);
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception {
|
||||
doThrow(new SecurityException()).when(mPackageManager).getPackageUid(PACKAGE_NAME, 0);
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
|
||||
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_automotive_failsIfAlreadySetByOtherPackage() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
|
||||
String otherPackage = "Raconteurs";
|
||||
when(mPackageManager.getPackageUid(otherPackage, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, otherPackage));
|
||||
assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE),
|
||||
contains(PACKAGE_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection_failsIfCannotLinkToDeath() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
doThrow(new RemoteException()).when(mBinder).linkToDeath(any(), anyInt());
|
||||
|
||||
assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestProjection() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
// Should work for all powers of two.
|
||||
for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
|
||||
assertTrue(mService.requestProjection(mBinder, p, PACKAGE_NAME));
|
||||
assertTrue((mService.getActiveProjectionTypes() & p) != 0);
|
||||
assertThat(mService.getProjectingPackages(p), contains(PACKAGE_NAME));
|
||||
// Subsequent calls should still succeed.
|
||||
assertTrue(mService.requestProjection(mBinder, p, PACKAGE_NAME));
|
||||
}
|
||||
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseProjection_failsForBogusPackageName() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
|
||||
.thenReturn(TestInjector.CALLING_UID + 1);
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.releaseProjection(
|
||||
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseProjection_failsIfNameNotFound() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
|
||||
.thenThrow(new PackageManager.NameNotFoundException());
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.releaseProjection(
|
||||
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
doThrow(new SecurityException()).when(mContext).enforceCallingPermission(
|
||||
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
|
||||
|
||||
// Should not be enforced for other types of projection.
|
||||
int nonAutomotiveProjectionType = PROJECTION_TYPE_AUTOMOTIVE * 2;
|
||||
mService.releaseProjection(nonAutomotiveProjectionType, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
|
||||
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseProjection() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
// Should work for all powers of two.
|
||||
for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
|
||||
mService.requestProjection(mBinder, p, PACKAGE_NAME);
|
||||
}
|
||||
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
|
||||
|
||||
assertTrue(mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
|
||||
int everythingButAutomotive = PROJECTION_TYPE_ALL & ~PROJECTION_TYPE_AUTOMOTIVE;
|
||||
assertEquals(everythingButAutomotive, mService.getActiveProjectionTypes());
|
||||
|
||||
for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
|
||||
assertEquals(p != PROJECTION_TYPE_AUTOMOTIVE,
|
||||
(boolean) mService.releaseProjection(p, PACKAGE_NAME));
|
||||
}
|
||||
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binderDeath_releasesProjection() throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
|
||||
mService.requestProjection(mBinder, p, PACKAGE_NAME);
|
||||
}
|
||||
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
|
||||
ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor = ArgumentCaptor.forClass(
|
||||
IBinder.DeathRecipient.class);
|
||||
verify(mBinder, atLeastOnce()).linkToDeath(deathRecipientCaptor.capture(), anyInt());
|
||||
|
||||
// Wipe them out. All of them.
|
||||
deathRecipientCaptor.getAllValues().forEach(IBinder.DeathRecipient::binderDied);
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getActiveProjectionTypes() throws Exception {
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProjectingPackages() throws Exception {
|
||||
assertTrue(mService.getProjectingPackages(PROJECTION_TYPE_ALL).isEmpty());
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE).size());
|
||||
assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_ALL).size());
|
||||
assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE),
|
||||
contains(PACKAGE_NAME));
|
||||
assertThat(mService.getProjectingPackages(PROJECTION_TYPE_ALL), contains(PACKAGE_NAME));
|
||||
mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertThat(mService.getProjectingPackages(PROJECTION_TYPE_ALL), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addOnProjectionStateChangeListener_enforcesReadProjStatePermission() {
|
||||
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
|
||||
eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.addOnProjectionStateChangeListener(
|
||||
listener, PROJECTION_TYPE_ALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addOnProjectionStateChangeListener_callsListenerIfProjectionActive()
|
||||
throws Exception {
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
|
||||
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
when(listener.asBinder()).thenReturn(mBinder); // Any binder will do
|
||||
mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
|
||||
verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE),
|
||||
eq(List.of(PACKAGE_NAME)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeOnProjectionStateChangeListener_enforcesReadProjStatePermission() {
|
||||
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
|
||||
eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
|
||||
assertThrows(SecurityException.class, () -> mService.removeOnProjectionStateChangeListener(
|
||||
listener));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeOnProjectionStateChangeListener() throws Exception {
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
|
||||
mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
|
||||
|
||||
mService.removeOnProjectionStateChangeListener(listener);
|
||||
// Now set automotive projection, should not call back.
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
verify(listener, never()).onProjectionStateChanged(anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectionStateChangeListener_calledWhenStateChanges() throws Exception {
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
|
||||
mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
|
||||
verify(listener, atLeastOnce()).asBinder(); // Called twice during register.
|
||||
|
||||
// No calls initially, no projection state set.
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
// Now set automotive projection, should call back.
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE),
|
||||
eq(List.of(PACKAGE_NAME)));
|
||||
|
||||
// Subsequent calls that are noops do nothing.
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
int unsetProjectionType = 0x0002;
|
||||
mService.releaseProjection(unsetProjectionType, PACKAGE_NAME);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
// Release should call back though.
|
||||
mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_NONE),
|
||||
eq(List.of()));
|
||||
|
||||
// But only the first time.
|
||||
mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectionStateChangeListener_calledForAnyRelevantStateChange() throws Exception {
|
||||
int fakeProjectionType = 0x0002;
|
||||
int otherFakeProjectionType = 0x0004;
|
||||
String otherPackageName = "Internet Arms";
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
when(mPackageManager.getPackageUid(otherPackageName, 0))
|
||||
.thenReturn(TestInjector.CALLING_UID);
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
|
||||
IOnProjectionStateChangeListener listener2 = mock(IOnProjectionStateChangeListener.class);
|
||||
when(listener2.asBinder()).thenReturn(mBinder); // Any binder will do.
|
||||
mService.addOnProjectionStateChangeListener(listener, fakeProjectionType);
|
||||
mService.addOnProjectionStateChangeListener(listener2,
|
||||
fakeProjectionType | otherFakeProjectionType);
|
||||
verify(listener, atLeastOnce()).asBinder(); // Called twice during register.
|
||||
verify(listener2, atLeastOnce()).asBinder(); // Called twice during register.
|
||||
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
verifyNoMoreInteractions(listener, listener2);
|
||||
|
||||
// fakeProjectionType should trigger both.
|
||||
mService.requestProjection(mBinder, fakeProjectionType, PACKAGE_NAME);
|
||||
verify(listener).onProjectionStateChanged(eq(fakeProjectionType),
|
||||
eq(List.of(PACKAGE_NAME)));
|
||||
verify(listener2).onProjectionStateChanged(eq(fakeProjectionType),
|
||||
eq(List.of(PACKAGE_NAME)));
|
||||
|
||||
// otherFakeProjectionType should only trigger the second listener.
|
||||
mService.requestProjection(mBinder, otherFakeProjectionType, otherPackageName);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verify(listener2).onProjectionStateChanged(
|
||||
eq(fakeProjectionType | otherFakeProjectionType),
|
||||
eq(List.of(PACKAGE_NAME, otherPackageName)));
|
||||
|
||||
// Turning off fakeProjectionType should trigger both again.
|
||||
mService.releaseProjection(fakeProjectionType, PACKAGE_NAME);
|
||||
verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_NONE), eq(List.of()));
|
||||
verify(listener2).onProjectionStateChanged(eq(otherFakeProjectionType),
|
||||
eq(List.of(otherPackageName)));
|
||||
|
||||
// Turning off otherFakeProjectionType should only trigger the second listener.
|
||||
mService.releaseProjection(otherFakeProjectionType, otherPackageName);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verify(listener2).onProjectionStateChanged(eq(PROJECTION_TYPE_NONE), eq(List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectionStateChangeListener_unregisteredOnDeath() throws Exception {
|
||||
IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
|
||||
IBinder listenerBinder = mock(IBinder.class);
|
||||
when(listener.asBinder()).thenReturn(listenerBinder);
|
||||
mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
|
||||
ArgumentCaptor<IBinder.DeathRecipient> listenerDeathRecipient = ArgumentCaptor.forClass(
|
||||
IBinder.DeathRecipient.class);
|
||||
verify(listenerBinder).linkToDeath(listenerDeathRecipient.capture(), anyInt());
|
||||
|
||||
// Now kill the binder for the listener. This should remove it from the list of listeners.
|
||||
listenerDeathRecipient.getValue().binderDied();
|
||||
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
|
||||
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
|
||||
verify(listener, never()).onProjectionStateChanged(anyInt(), any());
|
||||
}
|
||||
|
||||
private static class TestInjector extends UiModeManagerService.Injector {
|
||||
private static final int CALLING_UID = 8675309;
|
||||
|
||||
public int getCallingUid() {
|
||||
return CALLING_UID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user