diff options
124 files changed, 3976 insertions, 914 deletions
diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto index 8e29f9645568..23e951992305 100644 --- a/cmds/am/proto/instrumentation_data.proto +++ b/cmds/am/proto/instrumentation_data.proto @@ -38,6 +38,7 @@ message ResultsBundle { message TestStatus { optional sint32 result_code = 3; optional ResultsBundle results = 4; + optional string logcat = 5; } enum SessionStatusCode { diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index 70baa8702ba9..4d7b5a79b4f7 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -38,6 +38,7 @@ import android.view.IWindowManager; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -62,8 +63,15 @@ import java.util.Locale; * other: Failure */ public class Instrument { + private static final String TAG = "am"; + public static final String DEFAULT_LOG_DIR = "instrument-logs"; + private static final int STATUS_TEST_PASSED = 0; + private static final int STATUS_TEST_STARTED = 1; + private static final int STATUS_TEST_FAILED_ASSERTION = -1; + private static final int STATUS_TEST_FAILED_OTHER = -2; + private final IActivityManager mAm; private final IPackageManager mPm; private final IWindowManager mWm; @@ -207,6 +215,8 @@ public class Instrument { private File mLog; + private long mTestStartMs; + ProtoStatusReporter() { if (protoFile) { if (logPath == null) { @@ -241,10 +251,22 @@ public class Instrument { Bundle results) { final ProtoOutputStream proto = new ProtoOutputStream(); - final long token = proto.start(InstrumentationData.Session.TEST_STATUS); + final long testStatusToken = proto.start(InstrumentationData.Session.TEST_STATUS); + proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode); writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results); - proto.end(token); + + if (resultCode == STATUS_TEST_STARTED) { + // Logcat -T takes wall clock time (!?) + mTestStartMs = System.currentTimeMillis(); + } else { + if (mTestStartMs > 0) { + proto.write(InstrumentationData.TestStatus.LOGCAT, readLogcat(mTestStartMs)); + } + mTestStartMs = 0; + } + + proto.end(testStatusToken); outputProto(proto); } @@ -254,12 +276,12 @@ public class Instrument { Bundle results) { final ProtoOutputStream proto = new ProtoOutputStream(); - final long token = proto.start(InstrumentationData.Session.SESSION_STATUS); + final long sessionStatusToken = proto.start(InstrumentationData.Session.SESSION_STATUS); proto.write(InstrumentationData.SessionStatus.STATUS_CODE, InstrumentationData.SESSION_FINISHED); proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode); writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results); - proto.end(token); + proto.end(sessionStatusToken); outputProto(proto); } @@ -268,11 +290,11 @@ public class Instrument { public void onError(String errorText, boolean commandError) { final ProtoOutputStream proto = new ProtoOutputStream(); - final long token = proto.start(InstrumentationData.Session.SESSION_STATUS); + final long sessionStatusToken = proto.start(InstrumentationData.Session.SESSION_STATUS); proto.write(InstrumentationData.SessionStatus.STATUS_CODE, InstrumentationData.SESSION_ABORTED); proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText); - proto.end(token); + proto.end(sessionStatusToken); outputProto(proto); } @@ -514,5 +536,43 @@ public class Instrument { } } } + + private static String readLogcat(long startTimeMs) { + try { + // Figure out the timestamp arg for logcat. + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + final String timestamp = format.format(new Date(startTimeMs)); + + // Start the process + final Process process = new ProcessBuilder() + .command("logcat", "-d", "-v threadtime,uid", "-T", timestamp) + .start(); + + // Nothing to write. Don't let the command accidentally block. + process.getOutputStream().close(); + + // Read the output + final StringBuilder str = new StringBuilder(); + final InputStreamReader reader = new InputStreamReader(process.getInputStream()); + char[] buffer = new char[4096]; + int amt; + while ((amt = reader.read(buffer, 0, buffer.length)) >= 0) { + if (amt > 0) { + str.append(buffer, 0, amt); + } + } + + try { + process.waitFor(); + } catch (InterruptedException ex) { + // We already have the text, drop the exception. + } + + return str.toString(); + + } catch (IOException ex) { + return "Error reading logcat command:\n" + ex.toString(); + } + } } diff --git a/cmds/incidentd/OWNERS b/cmds/incidentd/OWNERS index cede4eae50ad..bcdcfc3cafed 100644 --- a/cmds/incidentd/OWNERS +++ b/cmds/incidentd/OWNERS @@ -1,3 +1,4 @@ joeo@google.com -kwekua@google.com +yaochen@google.com yanmin@google.com +zhouwenjie@google.com diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS index 13157505fc28..380e499a5abf 100644 --- a/cmds/statsd/OWNERS +++ b/cmds/statsd/OWNERS @@ -1,11 +1,7 @@ -bookatz@google.com -cjyu@google.com -dwchen@google.com -jinyithu@google.com +jianjin@google.com joeo@google.com -kwekua@google.com +jtnguyen@google.com +muhammadq@google.com singhtejinder@google.com -stlafon@google.com yaochen@google.com -yanglu@google.com yro@google.com diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 318d90ca9199..3a84b79fc681 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -48,6 +48,7 @@ import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; import "frameworks/base/core/proto/android/stats/intelligence/enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; +import "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.proto"; import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto"; import "frameworks/base/core/proto/android/stats/style/style_enums.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; @@ -283,6 +284,16 @@ message Atom { ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed = 189; RoleRequestResultReported role_request_result_reported = 190 [(log_from_module) = "permissioncontroller"]; + MediametricsAudiopolicyReported mediametrics_audiopolicy_reported = 191; + MediametricsAudiorecordReported mediametrics_audiorecord_reported = 192; + MediametricsAudiothreadReported mediametrics_audiothread_reported = 193; + MediametricsAudiotrackReported mediametrics_audiotrack_reported = 194; + MediametricsCodecReported mediametrics_codec_reported = 195; + MediametricsDrmWidevineReported mediametrics_drm_widevine_reported = 196; + MediametricsExtractorReported mediametrics_extractor_reported = 197; + MediametricsMediadrmReported mediametrics_mediadrm_reported = 198; + MediametricsNuPlayerReported mediametrics_nuplayer_reported = 199; + MediametricsRecorderReported mediametrics_recorder_reported = 200; } // Pulled events will start at field 10000. @@ -5815,6 +5826,160 @@ message ProcessStartTime { } /** + * Track Media Codec usage + * Logged from: + * frameworks/av/media/libstagefright/MediaCodec.cpp + * frameworks/av/services/mediaanalytics/statsd_codec.cpp + */ +message MediametricsCodecReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.CodecData codec_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track Media Extractor (pulling video/audio streams out of containers) usage + * Logged from: + * frameworks/av/media/libstagefright/RemoteMediaExtractor.cpp + * frameworks/av/services/mediaanalytics/statsd_extractor.cpp + */ +message MediametricsExtractorReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.ExtractorData extractor_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track how we arbitrate between microphone/input requests. + * Logged from + * frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp + * frameworks/av/services/mediaanalytics/statsd_audiopolicy.cpp + */ +message MediametricsAudiopolicyReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.AudioPolicyData audiopolicy_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track how we arbitrate between microphone requests. + * Logged from + * frameworks/av/media/libaudioclient/AudioRecord.cpp + * frameworks/av/services/mediaanalytics/statsd_audiorecord.cpp + */ +message MediametricsAudiorecordReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.AudioRecordData audiorecord_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track how we arbitrate between microphone/input requests. + * Logged from + * frameworks/av/media/libnblog/ReportPerformance.cpp + * frameworks/av/services/mediaanalytics/statsd_audiothread.cpp + */ +message MediametricsAudiothreadReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.AudioThreadData audiothread_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track how we arbitrate between microphone/input requests. + * Logged from + * frameworks/av/media/libaudioclient/AudioTrack.cpp + * frameworks/av/services/mediaanalytics/statsd_audiotrack.cpp + */ +message MediametricsAudiotrackReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.AudioTrackData audiotrack_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track information about DRM framework performance + * Logged from + * frameworks/av/drm/libmediadrm/DrmHal.cpp + * frameworks/av/services/mediaanalytics/statsd_drm.cpp + */ +message MediametricsMediadrmReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + // vendor+description tell about which DRM plugin is in use on this device + optional string vendor = 5; + optional string description = 6; + // from frameworks/av/drm/libmediadrm/protos/metrics.proto + optional bytes framework_stats = 7 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track information about the widevine DRM plugin performance + * Logged from + * vendor/widevine/libwvdrmengine/cdm/metrics + * frameworks/av/services/mediaanalytics/statsd_drm.cpp + */ +message MediametricsDrmWidevineReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional bytes vendor_specific_stats = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track information about recordings (e.g. camcorder) + * Logged from + * frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp + * frameworks/av/services/mediaanalytics/statsd_recorder.cpp + */ +message MediametricsRecorderReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.RecorderData recorder_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Track Media Player usage + * Logged from: + * frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp + * frameworks/av/services/mediaanalytics/statsd_nuplayer.cpp + */ +message MediametricsNuPlayerReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + optional android.stats.mediametrics.NuPlayerData nuplayer_data = 5 [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** * State of a dangerous permission requested by a package */ message DangerousPermissionState { diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index fc7b778b7a86..3e705fdf8a12 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -57,6 +57,7 @@ public class TestDrive { "AID_BLUETOOTH", "AID_LMKD", "com.android.managedprovisioning", + "AID_MEDIA", "AID_NETWORK_STACK" }; private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 1b7fbfe0e32c..052445095afe 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -95,7 +95,8 @@ public final class Telecom extends BaseCommand { + "telecom set-phone-account-disabled: Disables the given phone account, if it \n" + " has already been registered with telecom.\n" + "\n" - + "telecom set-default-dialer: Sets the default dialer to the given component. \n" + + "telecom set-default-dialer: Sets the override default dialer to the given " + + "component; this will override whatever the dialer role is set to. \n" + "\n" + "telecom get-default-dialer: Displays the current default dialer. \n" + "\n" @@ -254,13 +255,8 @@ public final class Telecom extends BaseCommand { private void runSetDefaultDialer() throws RemoteException { final String packageName = nextArgRequired(); - final boolean success = mTelecomService.setDefaultDialer(packageName); - if (success) { - System.out.println("Success - " + packageName + " set as default dialer."); - } else { - System.out.println("Error - " + packageName + " is not an installed Dialer app, \n" - + " or is already the default dialer."); - } + mTelecomService.setTestDefaultDialer(packageName); + System.out.println("Success - " + packageName + " set as override default dialer."); } private void runGetDefaultDialer() throws RemoteException { diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index fc6fffabc917..b64b2dcc4f61 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -317,7 +317,7 @@ public class ActivityView extends ViewGroup { * regions and avoid focus switches by touches on this view. */ public void onLocationChanged() { - updateTapExcludeRegion(); + updateLocationAndTapExcludeRegion(); } @Override @@ -329,39 +329,52 @@ public class ActivityView extends ViewGroup { public boolean gatherTransparentRegion(Region region) { // The tap exclude region may be affected by any view on top of it, so we detect the // possible change by monitoring this function. - updateTapExcludeRegion(); + updateLocationAndTapExcludeRegion(); return super.gatherTransparentRegion(region); } - /** Compute and send current tap exclude region to WM for this view. */ - private void updateTapExcludeRegion() { - if (!isAttachedToWindow()) { - return; - } - if (!canReceivePointerEvents()) { - cleanTapExcludeRegion(); + /** + * Sends current location in window and tap exclude region to WM for this view. + */ + private void updateLocationAndTapExcludeRegion() { + if (mVirtualDisplay == null || !isAttachedToWindow()) { return; } try { + int x = mLocationInWindow[0]; + int y = mLocationInWindow[1]; getLocationInWindow(mLocationInWindow); - final int x = mLocationInWindow[0]; - final int y = mLocationInWindow[1]; - mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight()); - - // There might be views on top of us. We need to subtract those areas from the tap - // exclude region. - final ViewParent parent = getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).subtractObscuredTouchableRegion(mTapExcludeRegion, this); + if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) { + x = mLocationInWindow[0]; + y = mLocationInWindow[1]; + WindowManagerGlobal.getWindowSession().updateDisplayContentLocation( + getWindow(), x, y, mVirtualDisplay.getDisplay().getDisplayId()); } - - WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), - mTapExcludeRegion); + updateTapExcludeRegion(x, y); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } } + /** Computes and sends current tap exclude region to WM for this view. */ + private void updateTapExcludeRegion(int x, int y) throws RemoteException { + if (!canReceivePointerEvents()) { + cleanTapExcludeRegion(); + return; + } + mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight()); + + // There might be views on top of us. We need to subtract those areas from the tap + // exclude region. + final ViewParent parent = getParent(); + if (parent != null) { + parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this); + } + + WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), + mTapExcludeRegion); + } + private class SurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { @@ -379,7 +392,7 @@ public class ActivityView extends ViewGroup { mVirtualDisplay.setDisplayState(true); } - updateTapExcludeRegion(); + updateLocationAndTapExcludeRegion(); } @Override @@ -387,7 +400,7 @@ public class ActivityView extends ViewGroup { if (mVirtualDisplay != null) { mVirtualDisplay.resize(width, height, getBaseDisplayDensity()); } - updateTapExcludeRegion(); + updateLocationAndTapExcludeRegion(); } @Override @@ -471,7 +484,8 @@ public class ActivityView extends ViewGroup { try { // TODO: Find a way to consolidate these calls to the server. - wm.reparentDisplayContent(displayId, mRootSurfaceControl); + WindowManagerGlobal.getWindowSession().reparentDisplayContent( + getWindow(), mRootSurfaceControl, displayId); wm.dontOverrideDisplayInfo(displayId); if (mSingleTaskInstance) { mActivityTaskManager.setDisplayToSingleTaskInstance(displayId); diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index f8dc20e9af09..7fa436084246 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -198,6 +198,8 @@ public final class UsageStatsManager { /** @hide */ public static final int REASON_SUB_USAGE_UNEXEMPTED_SYNC_SCHEDULED = 0x000E; /** @hide */ + public static final int REASON_SUB_USAGE_FOREGROUND_SERVICE_START = 0x000F; + /** @hide */ public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001; @@ -997,6 +999,9 @@ public final class UsageStatsManager { case REASON_SUB_USAGE_UNEXEMPTED_SYNC_SCHEDULED: sb.append("-uss"); break; + case REASON_SUB_USAGE_FOREGROUND_SERVICE_START: + sb.append("-fss"); + break; } break; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 5f34f1b2bc0d..804677648d09 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -16,6 +16,8 @@ package android.content; +import static android.provider.DocumentsContract.EXTRA_ORIENTATION; + import android.accounts.Account; import android.annotation.IntDef; import android.annotation.NonNull; @@ -40,6 +42,7 @@ import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.ImageDecoder.ImageInfo; import android.graphics.ImageDecoder.Source; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -56,6 +59,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.system.Int32Ref; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -3563,9 +3567,14 @@ public abstract class ContentResolver implements ContentInterface { // Convert to Point, since that's what the API is defined as final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_SIZE, Point.convert(size)); - - return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { - return content.openTypedAssetFile(uri, "image/*", opts, signal); + final Int32Ref orientation = new Int32Ref(0); + + Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { + final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts, + signal); + final Bundle extras = afd.getExtras(); + orientation.value = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; + return afd; }), (ImageDecoder decoder, ImageInfo info, Source source) -> { decoder.setAllocator(allocator); @@ -3581,6 +3590,20 @@ public abstract class ContentResolver implements ContentInterface { decoder.setTargetSampleSize(sample); } }); + + // Transform the bitmap if requested. We use a side-channel to + // communicate the orientation, since EXIF thumbnails don't contain + // the rotation flags of the original image. + if (orientation.value != 0) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + + final Matrix m = new Matrix(); + m.setRotate(orientation.value, width / 2, height / 2); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); + } + + return bitmap; } /** {@hide} */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3dd510c6a6e6..01123968fcbd 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3009,7 +3009,14 @@ public abstract class Context { /** * Variation of {@link #bindService} that, in the specific case of isolated * services, allows the caller to generate multiple instances of a service - * from a single component declaration. + * from a single component declaration. In other words, you can use this to bind + * to a service that has specified {@link android.R.attr#isolatedProcess} and, in + * addition to the existing behavior of running in an isolated process, you can + * also through the arguments here have the system bring up multiple concurrent + * processes hosting their own instances of that service. The <var>instanceName</var> + * you provide here identifies the different instances, and you can use + * {@link #updateServiceGroup(ServiceConnection, int, int)} to tell the system how it + * should manage each of these instances. * * @param service Identifies the service to connect to. The Intent must * specify an explicit component name. @@ -3027,6 +3034,8 @@ public abstract class Context { * @throws IllegalArgumentException If the instanceName is invalid. * * @see #bindService + * @see #updateServiceGroup + * @see android.R.attr#isolatedProcess */ public boolean bindIsolatedService(@RequiresPermission @NonNull Intent service, @BindServiceFlags int flags, @NonNull String instanceName, @@ -3082,10 +3091,16 @@ public abstract class Context { * are considered to be related. Supplying 0 reverts to the default behavior * of not grouping. * @param importance Additional importance of the processes within a group. Upon calling - * here, this will override any previous group that was set for that - * process. This fine-tunes process killing of all processes within - * a related groups -- higher importance values will be killed before - * lower ones. + * here, this will override any previous importance that was set for that + * process. The most important process is 0, and higher values are + * successively less important. You can view this as describing how + * to order the processes in an array, with the processes at the end of + * the array being the least important. This value has no meaning besides + * indicating how processes should be ordered in that array one after the + * other. This provides a way to fine-tune the system's process killing, + * guiding it to kill processes at the end of the array first. + * + * @see #bindIsolatedService */ public void updateServiceGroup(@NonNull ServiceConnection conn, int group, int importance) { diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java index 08afe31f05dd..1f61a3c47ff1 100644 --- a/core/java/android/os/IncidentManager.java +++ b/core/java/android/os/IncidentManager.java @@ -519,6 +519,13 @@ public class IncidentManager { android.Manifest.permission.PACKAGE_USAGE_STATS }) public @Nullable IncidentReport getIncidentReport(Uri uri) { + final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID); + if (id == null) { + // If there's no report id, it's a bug report, so we can't return the incident + // report. + return null; + } + final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE); if (pkg == null) { throw new RuntimeException("Invalid URI: No " @@ -531,13 +538,6 @@ public class IncidentManager { + URI_PARAM_RECEIVER_CLASS + " parameter. " + uri); } - final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID); - if (cls == null) { - // If there's no report id, it's a bug report, so we can't return the incident - // report. - return null; - } - try { return getCompanionServiceLocked().getIncidentReport(pkg, cls, id); } catch (RemoteException ex) { diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 7cc7ccd2ad16..1b41694e7d48 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -866,8 +866,8 @@ public class RecoverySystem { if (storageManager.needsCheckpoint()) { Log.i(TAG, "Rescue Party requested wipe. Aborting update instead."); storageManager.abortChanges("rescueparty", false); + return; } - return; } catch (RemoteException e) { Log.i(TAG, "Failed to handle with checkpointing. Continuing with wipe."); } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 4632b7553ca0..4ac485099da8 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -36,6 +36,7 @@ import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.Point; import android.media.ExifInterface; +import android.media.MediaFile; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -1698,6 +1699,18 @@ public final class DocumentsContract { } catch (IOException e) { } + // Use ImageDecoder to do full image decode of heif format file + // will have right orientation. So, we don't need to add orientation + // information into extras. + final String mimeType = MediaFile.getMimeTypeForFile(file.getName()); + if (mimeType.equals("image/heif") + || mimeType.equals("image/heif-sequence") + || mimeType.equals("image/heic") + || mimeType.equals("image/heic-sequence")) { + return new AssetFileDescriptor(pfd, 0 /* startOffset */, + AssetFileDescriptor.UNKNOWN_LENGTH, null /* extras */); + } + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b0e980e7206f..8231a92a6fbf 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11866,6 +11866,7 @@ public final class Settings { * sync_adapter_duration (long) * exempted_sync_duration (long) * system_interaction_duration (long) + * initial_foreground_service_start_duration (long) * stable_charging_threshold (long) * * idle_duration (long) // This is deprecated and used to circumvent b/26355386. diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index e8b0d92c2fd2..ae36e4ecde17 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -36,7 +36,6 @@ public class FeatureFlagUtils { public static final String PERSIST_PREFIX = "persist." + FFLAG_OVERRIDE_PREFIX; public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer"; public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; - public static final String SAFETY_HUB = "settings_safety_hub"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; public static final String FORCE_GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_force_grid_enabled"; @@ -57,7 +56,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(DYNAMIC_SYSTEM, "false"); DEFAULT_FLAGS.put(SEAMLESS_TRANSFER, "false"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); - DEFAULT_FLAGS.put(SAFETY_HUB, "true"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); DEFAULT_FLAGS.put(FORCE_GLOBAL_ACTIONS_GRID_ENABLED, "false"); DEFAULT_FLAGS.put(GLOBAL_ACTIONS_PANEL_ENABLED, "true"); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c730fe2dc114..b347a78a8780 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -621,18 +621,6 @@ interface IWindowManager */ void setShouldShowIme(int displayId, boolean shouldShow); - /** - * Reparent the top layers for a display to the requested surfaceControl. The display that - * is going to be re-parented (the displayId passed in) needs to have been created by the same - * process that is requesting the re-parent. This is to ensure clients can't just re-parent - * display content info to any SurfaceControl, as this would be a security issue. - * - * @param displayId The id of the display. - * @param surfaceControlHandle The SurfaceControl that the top level layers for the - * display should be re-parented to. - */ - void reparentDisplayContent(int displayId, in SurfaceControl sc); - /** * Waits for transactions to get applied before injecting input. * This includes waiting for the input windows to get sent to InputManager. diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index b52fdb8399d1..d269323d50a4 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -257,6 +257,31 @@ interface IWindowSession { void updatePointerIcon(IWindow window); /** + * Reparent the top layers for a display to the requested SurfaceControl. The display that is + * going to be re-parented (the displayId passed in) needs to have been created by the same + * process that is requesting the re-parent. This is to ensure clients can't just re-parent + * display content info to any SurfaceControl, as this would be a security issue. + * + * @param window The window which owns the SurfaceControl. This indicates the z-order of the + * windows of this display against the windows on the parent display. + * @param sc The SurfaceControl that the top level layers for the display should be re-parented + * to. + * @param displayId The id of the display to be re-parented. + */ + void reparentDisplayContent(IWindow window, in SurfaceControl sc, int displayId); + + /** + * Update the location of a child display in its parent window. This enables windows in the + * child display to compute the global transformation matrix. + * + * @param window The parent window of the display. + * @param x The x coordinate in the parent window. + * @param y The y coordinate in the parent window. + * @param displayId The id of the display to be notified. + */ + void updateDisplayContentLocation(IWindow window, int x, int y, int displayId); + + /** * Update a tap exclude region identified by provided id in the window. Touches on this region * will neither be dispatched to this window nor change the focus to this window. Passing an * invalid region will remove the area from the exclude region of this window. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5929c1b4d6e6..921294a78e5e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7054,10 +7054,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private FrameMetricsObserver findFrameMetricsObserver( Window.OnFrameMetricsAvailableListener listener) { - for (int i = 0; i < mFrameMetricsObservers.size(); i++) { - FrameMetricsObserver observer = mFrameMetricsObservers.get(i); - if (observer.mListener == listener) { - return observer; + if (mFrameMetricsObservers != null) { + for (int i = 0; i < mFrameMetricsObservers.size(); i++) { + FrameMetricsObserver observer = mFrameMetricsObservers.get(i); + if (observer.mListener == listener) { + return observer; + } } } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index c2ad82fd5c5f..84608406061a 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -484,8 +484,17 @@ public final class ContentCaptureManager { Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext); } + MainContentCaptureSession mainSession; synchronized (mLock) { - mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP; + if (enabled) { + mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP; + } else { + mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP; + } + mainSession = mMainSession; + } + if (mainSession != null) { + mainSession.setDisabled(!enabled); } } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 784cf9c32557..8673fbe63091 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -28,6 +28,7 @@ import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APP import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -80,6 +81,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ public static final String EXTRA_BINDER = "binder"; + /** + * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state. + * @hide + */ + public static final String EXTRA_ENABLED_STATE = "enabled"; + @NonNull private final AtomicBoolean mDisabled = new AtomicBoolean(false); @@ -155,6 +162,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession { public void send(int resultCode, Bundle resultData) { final IBinder binder; if (resultData != null) { + // Change in content capture enabled. + final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE); + if (hasEnabled) { + final boolean disabled = (resultCode == RESULT_CODE_FALSE); + mDisabled.set(disabled); + return; + } binder = resultData.getBinder(EXTRA_BINDER); if (binder == null) { Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); @@ -578,6 +592,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { return mDisabled.get(); } + /** + * Called by ContentCaptureManager.setContentCaptureEnabled + * + * @return whether disabled state was changed. + */ + boolean setDisabled(boolean disabled) { + return mDisabled.compareAndSet(!disabled, disabled); + } + // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such // change should also get get rid of the "internalNotifyXXXX" methods above diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java index 87d80d408924..9ac979b54716 100644 --- a/core/java/com/android/internal/app/AbstractResolverComparator.java +++ b/core/java/com/android/internal/app/AbstractResolverComparator.java @@ -43,7 +43,7 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen private static final boolean DEBUG = false; private static final String TAG = "AbstractResolverComp"; - private AfterCompute mAfterCompute; + protected AfterCompute mAfterCompute; protected final PackageManager mPm; protected final UsageStatsManager mUsm; protected String[] mAnnotations; @@ -70,11 +70,7 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen Log.d(TAG, "RANKER_SERVICE_RESULT"); } if (mHandler.hasMessages(RANKER_RESULT_TIMEOUT)) { - if (msg.obj != null) { - handleResultMessage(msg); - } else { - Log.e(TAG, "Receiving null prediction results."); - } + handleResultMessage(msg); mHandler.removeMessages(RANKER_RESULT_TIMEOUT); afterCompute(); } @@ -99,7 +95,6 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen mHttp = "http".equals(scheme) || "https".equals(scheme); mContentType = intent.getType(); getContentAnnotations(intent); - mPm = context.getPackageManager(); mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); mDefaultBrowserPackageName = mHttp diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java index 3b4e1a0618a1..fb27a2ff8992 100644 --- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java +++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java @@ -26,9 +26,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.os.Handler; import android.os.Message; import android.os.UserHandle; -import android.view.textclassifier.Log; +import android.util.Log; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; @@ -38,27 +39,44 @@ import java.util.List; import java.util.Map; /** - * Uses an {@link AppPredictor} to sort Resolver targets. + * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be + * disabled by returning an empty sorted target list, {@link AppPredictionServiceResolverComparator} + * will fallback to using a {@link ResolverRankerServiceResolverComparator}. */ class AppPredictionServiceResolverComparator extends AbstractResolverComparator { private static final String TAG = "APSResolverComparator"; + private static final long DELAY_COMPUTE_WHEN_DEFAULTING_TO_RESOLVER_MILLIS = 200; private final AppPredictor mAppPredictor; private final Context mContext; private final Map<ComponentName, Integer> mTargetRanks = new HashMap<>(); private final UserHandle mUser; + private final Intent mIntent; + private final String mReferrerPackage; + // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall + // back to using the ResolverRankerService. + private ResolverRankerServiceResolverComparator mResolverRankerService; AppPredictionServiceResolverComparator( - Context context, Intent intent, AppPredictor appPredictor, UserHandle user) { + Context context, + Intent intent, + String referrerPackage, + AppPredictor appPredictor, + UserHandle user) { super(context, intent); mContext = context; + mIntent = intent; mAppPredictor = appPredictor; mUser = user; + mReferrerPackage = referrerPackage; } @Override int compare(ResolveInfo lhs, ResolveInfo rhs) { + if (mResolverRankerService != null) { + return mResolverRankerService.compare(lhs, rhs); + } Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName, lhs.activityInfo.name)); Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName, @@ -75,6 +93,10 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator @Override void doCompute(List<ResolvedComponentInfo> targets) { + if (targets.isEmpty()) { + mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT); + return; + } List<AppTarget> appTargets = new ArrayList<>(); for (ResolvedComponentInfo target : targets) { appTargets.add(new AppTarget.Builder(new AppTargetId(target.name.flattenToString())) @@ -83,25 +105,48 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator } mAppPredictor.sortTargets(appTargets, mContext.getMainExecutor(), sortedAppTargets -> { - Message msg = + if (sortedAppTargets.isEmpty()) { + // APS for chooser is disabled. Fallback to resolver. + mResolverRankerService = + new ResolverRankerServiceResolverComparator( + mContext, mIntent, mReferrerPackage, + () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT)); + mResolverRankerService.initRanker(mContext); + Handler computeHandler = + new Handler(msg -> { + mResolverRankerService.compute(targets); + return true; + }); + computeHandler.sendEmptyMessageDelayed( + 0, DELAY_COMPUTE_WHEN_DEFAULTING_TO_RESOLVER_MILLIS); + } else { + Message msg = Message.obtain(mHandler, RANKER_SERVICE_RESULT, sortedAppTargets); - msg.sendToTarget(); - }); + msg.sendToTarget(); + } + } + ); } @Override void handleResultMessage(Message msg) { - if (msg.what == RANKER_SERVICE_RESULT) { + // Null value is okay if we have defaulted to the ResolverRankerService. + if (msg.what == RANKER_SERVICE_RESULT && msg.obj != null) { final List<AppTarget> sortedAppTargets = (List<AppTarget>) msg.obj; for (int i = 0; i < sortedAppTargets.size(); i++) { mTargetRanks.put(new ComponentName(sortedAppTargets.get(i).getPackageName(), sortedAppTargets.get(i).getClassName()), i); } + } else if (msg.obj == null && mResolverRankerService == null) { + Log.e(TAG, "Unexpected null result"); } } @Override float getScore(ComponentName name) { + if (mResolverRankerService != null) { + return mResolverRankerService.getScore(name); + } Integer rank = mTargetRanks.get(name); if (rank == null) { Log.w(TAG, "Score requested for unknown component."); @@ -113,6 +158,10 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator @Override void updateModel(ComponentName componentName) { + if (mResolverRankerService != null) { + mResolverRankerService.updateModel(componentName); + return; + } mAppPredictor.notifyAppTargetEvent( new AppTargetEvent.Builder( new AppTarget.Builder( @@ -121,4 +170,12 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator .setClassName(componentName.getClassName()).build(), ACTION_LAUNCH).build()); } + + @Override + void destroy() { + if (mResolverRankerService != null) { + mResolverRankerService.destroy(); + mResolverRankerService = null; + } + } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 93c00d634b5e..659272c81733 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -453,6 +453,11 @@ public class ChooserActivity extends ResolverActivity { if (mChooserListAdapter == null) { return; } + if (resultList.isEmpty()) { + // APS may be disabled, so try querying targets ourselves. + queryDirectShareTargets(mChooserListAdapter, true); + return; + } final List<DisplayResolveInfo> driList = getDisplayResolveInfos(mChooserListAdapter); final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos = @@ -919,6 +924,8 @@ public class ChooserActivity extends ResolverActivity { mChooserHandler.removeMessages(LIST_VIEW_UPDATE_MESSAGE); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); + mChooserHandler.removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT); + mChooserHandler.removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED); if (mAppPredictor != null) { mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback); mAppPredictor.destroy(); @@ -1272,11 +1279,14 @@ public class ChooserActivity extends ResolverActivity { return driList; } - private void queryDirectShareTargets(ChooserListAdapter adapter) { - AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(); - if (appPredictor != null) { - appPredictor.requestPredictionUpdate(); - return; + private void queryDirectShareTargets( + ChooserListAdapter adapter, boolean skipAppPredictionService) { + if (!skipAppPredictionService) { + AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(); + if (appPredictor != null) { + appPredictor.requestPredictionUpdate(); + return; + } } // Default to just querying ShortcutManager if AppPredictor not present. final IntentFilter filter = getTargetIntentFilter(); @@ -1609,7 +1619,7 @@ public class ChooserActivity extends ResolverActivity { AbstractResolverComparator resolverComparator; if (appPredictor != null) { resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(), - appPredictor, getUser()); + getReferrerPackageName(), appPredictor, getUser()); } else { resolverComparator = new ResolverRankerServiceResolverComparator(this, getTargetIntent(), @@ -2205,7 +2215,7 @@ public class ChooserActivity extends ResolverActivity { Log.d(TAG, "querying direct share targets from ShortcutManager"); } - queryDirectShareTargets(this); + queryDirectShareTargets(this, false); } if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) { if (DEBUG) { diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java index a7819070ee98..d6334679e283 100644 --- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java @@ -66,9 +66,6 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator // timeout for establishing connections with a ResolverRankerService. private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200; - // timeout for establishing connections with a ResolverRankerService, collecting features and - // predicting ranking scores. - private static final int WATCHDOG_TIMEOUT_MILLIS = 500; private final Collator mCollator; private final Map<String, UsageStats> mStats; @@ -106,6 +103,10 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator if (msg.what != RANKER_SERVICE_RESULT) { return; } + if (msg.obj == null) { + Log.e(TAG, "Receiving null prediction results."); + return; + } final List<ResolverTarget> receivedTargets = (List<ResolverTarget>) msg.obj; if (receivedTargets != null && mTargets != null && receivedTargets.size() == mTargets.size()) { @@ -314,7 +315,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator } // connect to a ranking service. - private void initRanker(Context context) { + void initRanker(Context context) { synchronized (mLock) { if (mConnection != null && mRanker != null) { if (DEBUG) { diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 480b1eaaf98c..a3d4798647be 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -1,13 +1,8 @@ # Be sure you are familiar with proto when you modify this directory. # Metrics -bookatz@google.com -cjyu@google.com -jinyithu@google.com joeo@google.com -kwekua@google.com singhtejinder@google.com -yanglu@google.com yaochen@google.com yro@google.com diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto new file mode 100644 index 000000000000..34ed90a8c90c --- /dev/null +++ b/core/proto/android/stats/mediametrics/mediametrics.proto @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.stats.mediametrics; + +/** + * Track how we arbitrate between microphone/input requests. + * Logged from + * frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp + * frameworks/av/services/mediaanalytics/statsd_audiopolicy.cpp + * Next Tag: 10 + */ +message AudioPolicyData { + optional int32 status = 1; + optional string request_source = 2; + optional string request_package = 3; + optional int32 request_session = 4; + optional string request_device = 5; + optional string active_source = 6; + optional string active_package = 7; + optional int32 active_session = 8; + optional string active_device = 9; +} + +/** + * Track properties of audio recording + * Logged from + * frameworks/av/media/libaudioclient/AudioRecord.cpp + * frameworks/av/services/mediaanalytics/statsd_audiorecord.cpp + * Next Tag: 16 + */ +message AudioRecordData { + optional string encoding = 1; + optional string source = 2; + optional int32 latency = 3; + optional int32 samplerate = 4; + optional int32 channels = 5; + optional int64 created_millis = 6; + optional int64 duration_millis = 7; + optional int32 count = 8; + optional int32 error_code = 9; + optional string error_function = 10; + optional int32 port_id = 11; + optional int32 frame_count = 12; + optional string attributes = 13; + optional int64 channel_mask = 14; + optional int64 start_count = 15; + +} + +/** + * Track audio thread performance data + * Logged from + * frameworks/av/media/libnblog/ReportPerformance.cpp + * frameworks/av/services/mediaanalytics/statsd_audiothread.cpp + * Next Tag: 28 + */ +message AudioThreadData { + optional string type = 1; + optional int32 framecount = 2; + optional int32 samplerate = 3; + optional string work_millis_hist = 4; + optional string latency_millis_hist = 5; + optional string warmup_millis_hist = 6; + optional int64 underruns = 7; + optional int64 overruns = 8; + optional int64 active_millis = 9; + optional int64 duration_millis = 10; + + optional int32 id = 11; + optional int32 port_id = 12; + optional int32 sample_rate = 13; + optional int64 channel_mask = 14; + optional string encoding = 15; + optional int32 frame_count = 16; + optional string output_device = 17; + optional string input_device = 18; + optional double io_jitter_mean_millis = 19; + optional double io_jitter_stddev_millis = 20; + optional double process_time_mean_millis = 21; + optional double process_time_stddev_millis = 22; + optional double timestamp_jitter_mean_millis = 23; + optional double timestamp_jitter_stddev_millis = 24; + optional double latency_mean_millis = 25; + optional double latency_stddev_millis = 26; + +} + +/** + * Track audio track playback data + * Logged from + * frameworks/av/media/libaudioclient/AudioTrack.cpp + * frameworks/av/services/mediaanalytics/statsd_audiotrack.cpp + * Next Tag: 12 + */ +message AudioTrackData { + optional string stream_type = 1; + optional string content_type = 2; + optional string track_usage = 3; + optional int32 sample_rate = 4; + optional int64 channel_mask = 5; + + optional int32 underrun_frames = 6; + optional int32 startup_glitch = 7; + + optional int32 port_id = 8; + optional string encoding = 9; + optional int32 frame_count = 10; + optional string attributes = 11; + + +} + +/** + * Track Media Codec usage + * Logged from: + * frameworks/av/media/libstagefright/MediaCodec.cpp + * frameworks/av/services/mediaanalytics/statsd_codec.cpp + * Next Tag: 21 + */ +message CodecData { + optional string codec = 1; + optional string mime = 2; + optional string mode = 3; + optional int32 encoder = 4; + optional int32 secure = 5; + optional int32 width = 6; + optional int32 height = 7; + optional int32 rotation = 8; + optional int32 crypto = 9; + optional int32 profile = 10; + optional int32 level = 11; + optional int32 max_width = 12; + optional int32 max_height = 13; + optional int32 error_code = 14; + optional string error_state = 15; + optional int64 latency_max = 16; + optional int64 latency_min = 17; + optional int64 latency_avg = 18; + optional int64 latency_count = 19; + optional int64 latency_unknown = 20; +} + +/** + * Track Media Extractor (pulling video/audio streams out of containers) usage + * Logged from: + * frameworks/av/media/libstagefright/RemoteMediaExtractor.cpp + * frameworks/av/services/mediaanalytics/statsd_extractor.cpp + * Next Tag: 4 + */ +message ExtractorData { + optional string format = 1; + optional string mime = 2; + optional int32 tracks = 3; +} + +/** + * Track Media Player usage + * this handles both nuplayer and nuplayer2 + * Logged from: + * frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp + * frameworks/av/media/libmediaplayer2/nuplayer2/NuPlayer2Driver.cpp + * frameworks/av/services/mediaanalytics/statsd_nuplayer.cpp + * Next Tag: 21 + */ +message NuPlayerData { + optional string whichPlayer = 1; + + optional string video_mime = 2; + optional string video_codec = 3; + optional int32 width = 4; + optional int32 height = 5; + optional int64 frames = 6; + optional int64 frames_dropped = 7; + optional double framerate = 8; + optional string audio_mime = 9; + optional string audio_codec = 10; + optional int64 duration_millis = 11; + optional int64 playing_millis = 12; + optional int32 error = 13; + optional int32 error_code = 14; + optional string error_state = 15; + optional string data_source_type = 16; + optional int64 rebuffering_millis = 17; + optional int32 rebuffers = 18; + optional int32 rebuffer_at_exit = 19; + optional int64 frames_dropped_startup = 20; +} + +/** + * Track information about recordings (e.g. camcorder) + * Logged from + * frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp + * frameworks/av/services/mediaanalytics/if_statsd.cpp + * Next Tag: 22 + */ +message RecorderData { + optional string audio_mime = 1; + optional string video_mime = 2; + optional int32 video_profile = 3; + optional int32 video_level = 4; + optional int32 width = 5; + optional int32 height = 6; + optional int32 rotation = 7; + optional int32 framerate = 8; + optional int32 capture_fps = 9; + optional double capture_fps_enable = 10; + optional int64 duration_millis = 11; + optional int64 paused_millis = 12; + optional int32 paused_count = 13; + optional int32 audio_bitrate = 14; + optional int32 audio_channels = 15; + optional int32 audio_samplerate = 16; + optional int32 movie_timescale = 17; + optional int32 audio_timescale = 18; + optional int32 video_timescale = 19; + optional int32 video_bitrate = 20; + optional int32 iframe_interval = 21; +} diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml index 256d94e5c330..7098c958ce36 100644 --- a/core/res/res/layout/resolve_grid_item.xml +++ b/core/res/res/layout/resolve_grid_item.xml @@ -23,7 +23,7 @@ android:minHeight="100dp" android:gravity="center" android:paddingTop="24dp" - android:paddingBottom="8dp" + android:paddingBottom="12dp" android:paddingLeft="12dp" android:paddingRight="12dp" android:focusable="true" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 04ccb74dae3c..bca0e937cb70 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4006,7 +4006,7 @@ <string name="config_batterymeterPerimeterPath" translatable="false"> M3.5,2 v0 H1.33 C0.6,2 0,2.6 0,3.33 V13v5.67 C0,19.4 0.6,20 1.33,20 h9.33 C11.4,20 12,19.4 12,18.67 V13V3.33 C12,2.6 11.4,2 10.67,2 H8.5 V0 H3.5 z M2,18v-7V4h8v9v5H2L2,18z </string> - + <string name="config_batterymeterErrorPerimeterPath" translatable="false">@string/config_batterymeterPerimeterPath</string> <string name="config_batterymeterFillMask" translatable="false"> M2,18 v-14 h8 v14 z </string> @@ -4057,4 +4057,38 @@ <!-- Which binder services to include in incident reports containing restricted images. --> <string-array name="config_restrictedImagesServices" translatable="false"/> + + <!-- Messages that should not be shown to the user during face auth enrollment. This should be + used to hide messages that may be too chatty or messages that the user can't do much about. + Entries are defined in android.hardware.biometrics.face@1.0 types.hal --> + <integer-array name="config_face_acquire_enroll_ignorelist" translatable="false" > + </integer-array> + <!-- Same as the above, but are defined by vendorCodes --> + <integer-array name="config_face_acquire_vendor_enroll_ignorelist" translatable="false" > + </integer-array> + + <!-- Messages that should not be shown to the user during face authentication, on keyguard. + This includes both lockscreen and bouncer. This should be used to hide messages that may be + too chatty or messages that the user can't do much about. Entries are defined in + android.hardware.biometrics.face@1.0 types.hal --> + <integer-array name="config_face_acquire_keyguard_ignorelist" translatable="false" > + </integer-array> + <!-- Same as the above, but are defined by vendorCodes --> + <integer-array name="config_face_acquire_vendor_keyguard_ignorelist" translatable="false" > + </integer-array> + + <!-- Messages that should not be shown to the user during face authentication, on + BiometricPrompt. This should be used to hide messages that may be too chatty or messages that + the user can't do much about. Entries are defined in + android.hardware.biometrics.face@1.0 types.hal --> + <integer-array name="config_face_acquire_biometricprompt_ignorelist" translatable="false" > + </integer-array> + <!-- Same as the above, but are defined by vendorCodes --> + <integer-array name="config_face_acquire_vendor_biometricprompt_ignorelist" translatable="false" > + </integer-array> + + <!-- The component name for the default profile supervisor, which can be set as a profile owner + even after user setup is complete. The defined component should be used for supervision purposes + only. The component must be part of a system app. --> + <string name="config_defaultSupervisionProfileOwnerComponent" translatable="false"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fbe340ed61d3..32bd58e20efa 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2570,6 +2570,13 @@ <java-symbol type="string" name="face_authenticated_no_confirmation_required" /> <java-symbol type="string" name="face_authenticated_confirmation_required" /> + <java-symbol type="array" name="config_face_acquire_enroll_ignorelist" /> + <java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" /> + <java-symbol type="array" name="config_face_acquire_keyguard_ignorelist" /> + <java-symbol type="array" name="config_face_acquire_vendor_keyguard_ignorelist" /> + <java-symbol type="array" name="config_face_acquire_biometricprompt_ignorelist" /> + <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" /> + <!-- Face config --> <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" /> @@ -3240,6 +3247,7 @@ <java-symbol type="string" name="config_icon_mask" /> <java-symbol type="string" name="config_batterymeterPerimeterPath" /> + <java-symbol type="string" name="config_batterymeterErrorPerimeterPath" /> <java-symbol type="string" name="config_batterymeterFillMask" /> <java-symbol type="string" name="config_batterymeterBoltPath" /> <java-symbol type="string" name="config_batterymeterPowersavePath" /> @@ -3773,4 +3781,6 @@ <java-symbol type="dimen" name="chooser_direct_share_label_placeholder_max_width" /> <java-symbol type="layout" name="chooser_az_label_row" /> <java-symbol type="string" name="chooser_all_apps_button_label" /> + + <java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" /> </resources> diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp new file mode 100644 index 000000000000..37020fc8d79f --- /dev/null +++ b/data/etc/car/Android.bp @@ -0,0 +1,123 @@ +// Copyright (C} 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"}; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + + +// Privapp permission whitelist files + +prebuilt_etc { + name: "privapp_whitelist_android.car.cluster.loggingrenderer", + sub_dir: "permissions", + src: "android.car.cluster.loggingrenderer.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_android.car.cluster.sample", + sub_dir: "permissions", + src: "android.car.cluster.sample.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_android.car.usb.handler", + sub_dir: "permissions", + src: "android.car.usb.handler.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.carlauncher", + sub_dir: "permissions", + src: "com.android.car.carlauncher.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.dialer", + sub_dir: "permissions", + src: "com.android.car.dialer.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.hvac", + sub_dir: "permissions", + src: "com.android.car.hvac.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.media", + sub_dir: "permissions", + src: "com.android.car.media.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.notification", + sub_dir: "permissions", + src: "com.android.car.notification.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.radio", + sub_dir: "permissions", + src: "com.android.car.radio.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.settings", + sub_dir: "permissions", + src: "com.android.car.settings.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.themeplayground", + sub_dir: "permissions", + src: "com.android.car.themeplayground.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.trust", + sub_dir: "permissions", + src: "com.android.car.trust.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car", + sub_dir: "permissions", + src: "com.android.car.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.google.android.car.kitchensink", + sub_dir: "permissions", + src: "com.google.android.car.kitchensink.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.car.developeroptions", + sub_dir: "permissions", + src: "com.android.car.developeroptions.xml", + filename_from_src: true, + product_specific: true, +} diff --git a/data/etc/car/android.car.cluster.loggingrenderer.xml b/data/etc/car/android.car.cluster.loggingrenderer.xml new file mode 100644 index 000000000000..784e0e7dde14 --- /dev/null +++ b/data/etc/car/android.car.cluster.loggingrenderer.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="android.car.cluster.loggingrenderer"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/android.car.cluster.sample.xml b/data/etc/car/android.car.cluster.sample.xml new file mode 100644 index 000000000000..75c57b8c1d07 --- /dev/null +++ b/data/etc/car/android.car.cluster.sample.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="android.car.cluster.sample"> + <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/android.car.usb.handler.xml b/data/etc/car/android.car.usb.handler.xml new file mode 100644 index 000000000000..c67847c74cf8 --- /dev/null +++ b/data/etc/car/android.car.usb.handler.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="android.car.usb.handler"> + <permission name="android.permission.MANAGE_USB"/> + <permission name="android.permission.MANAGE_USERS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml new file mode 100644 index 000000000000..8ec1cd41dab4 --- /dev/null +++ b/data/etc/car/com.android.car.carlauncher.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.carlauncher"> + <permission name="android.permission.ACTIVITY_EMBEDDING"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml new file mode 100644 index 000000000000..76c8c622ba71 --- /dev/null +++ b/data/etc/car/com.android.car.developeroptions.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.developeroptions"> + <permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/> + <permission name="android.permission.ACCESS_NOTIFICATIONS"/> + <permission name="android.permission.BACKUP"/> + <permission name="android.permission.BATTERY_STATS"/> + <permission name="android.permission.BLUETOOTH_PRIVILEGED"/> + <permission name="android.permission.CHANGE_APP_IDLE_STATE"/> + <permission name="android.permission.CHANGE_CONFIGURATION"/> + <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/> + <permission name="android.permission.DELETE_PACKAGES"/> + <permission name="android.permission.FORCE_STOP_PACKAGES"/> + <permission name="android.permission.LOCAL_MAC_ADDRESS"/> + <permission name="android.permission.MANAGE_DEBUGGING"/> + <permission name="android.permission.MANAGE_FINGERPRINT"/> + <permission name="android.permission.MANAGE_USB"/> + <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MASTER_CLEAR"/> + <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> + <permission name="android.permission.MOVE_PACKAGE"/> + <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_SEARCH_INDEXABLES"/> + <permission name="android.permission.REBOOT"/> + <permission name="android.permission.SET_TIME"/> + <permission name="android.permission.STATUS_BAR"/> + <permission name="android.permission.TETHER_PRIVILEGED"/> + <permission name="android.permission.USE_RESERVED_DISK"/> + <permission name="android.permission.USER_ACTIVITY"/> + <permission name="android.permission.WRITE_APN_SETTINGS"/> + <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.dialer.xml b/data/etc/car/com.android.car.dialer.xml new file mode 100644 index 000000000000..d44f5a1704a2 --- /dev/null +++ b/data/etc/car/com.android.car.dialer.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.dialer"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.hvac.xml b/data/etc/car/com.android.car.hvac.xml new file mode 100644 index 000000000000..d3631e067f8b --- /dev/null +++ b/data/etc/car/com.android.car.hvac.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.hvac"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.media.xml b/data/etc/car/com.android.car.media.xml new file mode 100644 index 000000000000..d17453d26e59 --- /dev/null +++ b/data/etc/car/com.android.car.media.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.media"> + <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.notification.xml b/data/etc/car/com.android.car.notification.xml new file mode 100644 index 000000000000..8479512b049e --- /dev/null +++ b/data/etc/car/com.android.car.notification.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.notification"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.radio.xml b/data/etc/car/com.android.car.radio.xml new file mode 100644 index 000000000000..d7853aba2d90 --- /dev/null +++ b/data/etc/car/com.android.car.radio.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.radio"> + <permission name="android.permission.ACCESS_BROADCAST_RADIO"/> + <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.settings.xml b/data/etc/car/com.android.car.settings.xml new file mode 100644 index 000000000000..5f7e1c13decf --- /dev/null +++ b/data/etc/car/com.android.car.settings.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.settings"> + <permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/> + <permission name="android.permission.ACCESS_NOTIFICATIONS"/> + <permission name="android.permission.BACKUP"/> + <permission name="android.permission.BATTERY_STATS"/> + <permission name="android.permission.BLUETOOTH_PRIVILEGED"/> + <permission name="android.permission.CHANGE_APP_IDLE_STATE"/> + <permission name="android.permission.CHANGE_CONFIGURATION"/> + <permission name="android.permission.DELETE_PACKAGES"/> + <permission name="android.permission.DELETE_CACHE_FILES"/> + <permission name="android.permission.DUMP"/> + <permission name="android.permission.FORCE_STOP_PACKAGES"/> + <permission name="android.permission.LOCAL_MAC_ADDRESS"/> + <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> + <permission name="android.permission.MANAGE_DEBUGGING"/> + <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> + <permission name="android.permission.MANAGE_FINGERPRINT"/> + <permission name="android.permission.MANAGE_USB"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" /> + <permission name="android.permission.MASTER_CLEAR"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> + <permission name="android.permission.MOVE_PACKAGE"/> + <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_SEARCH_INDEXABLES"/> + <permission name="android.permission.REBOOT"/> + <permission name="android.permission.SET_TIME"/> + <permission name="android.permission.SET_TIME_ZONE"/> + <permission name="android.permission.STATUS_BAR"/> + <permission name="android.permission.TETHER_PRIVILEGED"/> + <permission name="android.permission.USE_RESERVED_DISK"/> + <permission name="android.permission.USER_ACTIVITY"/> + <permission name="android.permission.WRITE_APN_SETTINGS"/> + <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.themeplayground.xml b/data/etc/car/com.android.car.themeplayground.xml new file mode 100644 index 000000000000..cab4718b0461 --- /dev/null +++ b/data/etc/car/com.android.car.themeplayground.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.themeplayground"> + <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.trust.xml b/data/etc/car/com.android.car.trust.xml new file mode 100644 index 000000000000..dc87af278b27 --- /dev/null +++ b/data/etc/car/com.android.car.trust.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.trust"> + <permission name="android.permission.BLUETOOTH" /> + <permission name="android.permission.BLUETOOTH_ADMIN" /> + <permission name="android.permission.ACCESS_COARSE_LOCATION"/> + <permission name="android.permission.ACCESS_FINE_LOCATION"/> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <permission name="android.permission.MANAGE_USERS" /> + <permission name="android.permission.CONTROL_KEYGUARD" /> + <permission name="android.permission.PROVIDE_TRUST_AGENT" /> + <permission name="android.permission.RECEIVE_BOOT_COMPLETED" /> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml new file mode 100644 index 000000000000..f1797deed075 --- /dev/null +++ b/data/etc/car/com.android.car.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car"> + <permission name="android.permission.LOCATION_HARDWARE"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MODIFY_AUDIO_ROUTING"/> + <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.PROVIDE_TRUST_AGENT"/> + <permission name="android.permission.REAL_GET_TASKS"/> + <permission name="android.permission.REBOOT"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml new file mode 100644 index 000000000000..6b26e8f3d5f2 --- /dev/null +++ b/data/etc/car/com.google.android.car.kitchensink.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.google.android.car.kitchensink"> + <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> + <permission name="android.permission.LOCATION_HARDWARE"/> + <permission name="android.permission.MANAGE_USB"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MODIFY_AUDIO_ROUTING"/> + <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.PROVIDE_TRUST_AGENT"/> + <permission name="android.permission.REAL_GET_TASKS"/> + <permission name="android.permission.REBOOT"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 485add9fa11f..27e859c3987c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -83,6 +83,7 @@ applications that come with the platform <permission name="android.permission.SET_TIME_ZONE"/> <permission name="android.permission.SHUTDOWN"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> </privapp-permissions> <privapp-permissions package="com.android.mms.service"> @@ -235,6 +236,7 @@ applications that come with the platform <permission name="android.permission.CALL_PRIVILEGED"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MANAGE_ROLE_HOLDERS"/> <permission name="android.permission.MODIFY_AUDIO_ROUTING" /> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.STOP_APP_SWITCHES"/> diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 816d1fda57d1..11d635eaba12 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -637,6 +637,7 @@ public class GradientDrawable extends Drawable { * @see #setOrientation(Orientation) */ public Orientation getOrientation() { + updateGradientStateOrientation(); return mGradientState.mOrientation; } @@ -653,6 +654,9 @@ public class GradientDrawable extends Drawable { * @see #getOrientation() */ public void setOrientation(Orientation orientation) { + // Update the angle here so that subsequent attempts to obtain the orientation + // from the angle overwrite previously configured values during inflation + mGradientState.mAngle = getAngleFromOrientation(orientation); mGradientState.mOrientation = orientation; mGradientIsDirty = true; invalidateSelf(); @@ -1242,6 +1246,76 @@ public class GradientDrawable extends Drawable { } /** + * Update the orientation of the gradient based on the given angle only if the type is + * {@link #LINEAR_GRADIENT} + */ + private void updateGradientStateOrientation() { + if (mGradientState.mGradient == LINEAR_GRADIENT) { + int angle = mGradientState.mAngle; + if (angle % 45 != 0) { + throw new IllegalArgumentException("Linear gradient requires 'angle' attribute to " + + "be a multiple of 45"); + } + + Orientation orientation; + switch (angle) { + case 0: + orientation = Orientation.LEFT_RIGHT; + break; + case 45: + orientation = Orientation.BL_TR; + break; + case 90: + orientation = Orientation.BOTTOM_TOP; + break; + case 135: + orientation = Orientation.BR_TL; + break; + case 180: + orientation = Orientation.RIGHT_LEFT; + break; + case 225: + orientation = Orientation.TR_BL; + break; + case 270: + orientation = Orientation.TOP_BOTTOM; + break; + case 315: + orientation = Orientation.TL_BR; + break; + default: + // Should not get here as exception is thrown above if angle is not multiple + // of 45 degrees + orientation = Orientation.LEFT_RIGHT; + break; + } + mGradientState.mOrientation = orientation; + } + } + + private int getAngleFromOrientation(Orientation orientation) { + switch (orientation) { + default: + case LEFT_RIGHT: + return 0; + case BL_TR: + return 45; + case BOTTOM_TOP: + return 90; + case BR_TL: + return 135; + case RIGHT_LEFT: + return 180; + case TR_BL: + return 225; + case TOP_BOTTOM: + return 270; + case TL_BR: + return 315; + } + } + + /** * This checks mGradientIsDirty, and if it is true, recomputes both our drawing * rectangle (mRect) and the gradient itself, since it depends on our * rectangle too. @@ -1270,6 +1344,7 @@ public class GradientDrawable extends Drawable { if (st.mGradient == LINEAR_GRADIENT) { final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; + updateGradientStateOrientation(); switch (st.mOrientation) { case TOP_BOTTOM: x0 = r.left; y0 = r.top; @@ -1312,10 +1387,6 @@ public class GradientDrawable extends Drawable { y0 = r.top + (r.bottom - r.top) * st.mCenterY; float radius = st.mGradientRadius; - if (Float.compare(radius, 0.0f) == -1 || Float.isNaN(radius)) { - throw new IllegalArgumentException("Gradient radius must be a valid " - + "number greater than or equal to 0"); - } if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { // Fall back to parent width or height if intrinsic // size is not specified. @@ -1511,8 +1582,6 @@ public class GradientDrawable extends Drawable { st.mAttrGradient, R.styleable.GradientDrawableGradient); try { updateGradientDrawableGradient(t.getResources(), a); - } catch (XmlPullParserException e) { - rethrowAsRuntimeException(e); } finally { a.recycle(); } @@ -1700,8 +1769,7 @@ public class GradientDrawable extends Drawable { } } - private void updateGradientDrawableGradient(Resources r, TypedArray a) - throws XmlPullParserException { + private void updateGradientDrawableGradient(Resources r, TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. @@ -1764,42 +1832,7 @@ public class GradientDrawable extends Drawable { } int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); - angle %= 360; - - if (angle % 45 != 0) { - throw new XmlPullParserException(a.getPositionDescription() - + "<gradient> tag requires 'angle' attribute to " - + "be a multiple of 45"); - } - - st.mAngle = angle; - - switch (angle) { - case 0: - st.mOrientation = Orientation.LEFT_RIGHT; - break; - case 45: - st.mOrientation = Orientation.BL_TR; - break; - case 90: - st.mOrientation = Orientation.BOTTOM_TOP; - break; - case 135: - st.mOrientation = Orientation.BR_TL; - break; - case 180: - st.mOrientation = Orientation.RIGHT_LEFT; - break; - case 225: - st.mOrientation = Orientation.TR_BL; - break; - case 270: - st.mOrientation = Orientation.TOP_BOTTOM; - break; - case 315: - st.mOrientation = Orientation.TL_BR; - break; - } + st.mAngle = angle % 360; final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); if (tv != null) { diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 977e790eb42e..8612e1bf324c 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -816,6 +816,8 @@ public final class AudioAttributes implements Parcelable { */ @UnsupportedAppUsage public Builder setInternalLegacyStreamType(int streamType) { + mContentType = CONTENT_TYPE_UNKNOWN; + mUsage = USAGE_UNKNOWN; if (AudioProductStrategy.getAudioProductStrategies().size() > 0) { AudioAttributes attributes = AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType( @@ -828,49 +830,52 @@ public final class AudioAttributes implements Parcelable { mTags = attributes.mTags; mBundle = attributes.mBundle; mSource = attributes.mSource; - return this; } } - switch(streamType) { - case AudioSystem.STREAM_VOICE_CALL: - mContentType = CONTENT_TYPE_SPEECH; - break; - case AudioSystem.STREAM_SYSTEM_ENFORCED: - mFlags |= FLAG_AUDIBILITY_ENFORCED; - // intended fall through, attributes in common with STREAM_SYSTEM - case AudioSystem.STREAM_SYSTEM: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_RING: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_MUSIC: - mContentType = CONTENT_TYPE_MUSIC; - break; - case AudioSystem.STREAM_ALARM: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_NOTIFICATION: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_BLUETOOTH_SCO: - mContentType = CONTENT_TYPE_SPEECH; - mFlags |= FLAG_SCO; - break; - case AudioSystem.STREAM_DTMF: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_TTS: - mContentType = CONTENT_TYPE_SONIFICATION; - mFlags |= FLAG_BEACON; - break; - case AudioSystem.STREAM_ACCESSIBILITY: - mContentType = CONTENT_TYPE_SPEECH; - break; - default: - Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes"); + if (mContentType == CONTENT_TYPE_UNKNOWN) { + switch (streamType) { + case AudioSystem.STREAM_VOICE_CALL: + mContentType = CONTENT_TYPE_SPEECH; + break; + case AudioSystem.STREAM_SYSTEM_ENFORCED: + mFlags |= FLAG_AUDIBILITY_ENFORCED; + // intended fall through, attributes in common with STREAM_SYSTEM + case AudioSystem.STREAM_SYSTEM: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_RING: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_MUSIC: + mContentType = CONTENT_TYPE_MUSIC; + break; + case AudioSystem.STREAM_ALARM: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_NOTIFICATION: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_BLUETOOTH_SCO: + mContentType = CONTENT_TYPE_SPEECH; + mFlags |= FLAG_SCO; + break; + case AudioSystem.STREAM_DTMF: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_TTS: + mContentType = CONTENT_TYPE_SONIFICATION; + mFlags |= FLAG_BEACON; + break; + case AudioSystem.STREAM_ACCESSIBILITY: + mContentType = CONTENT_TYPE_SPEECH; + break; + default: + Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes"); + } + } + if (mUsage == USAGE_UNKNOWN) { + mUsage = usageForStreamType(streamType); } - mUsage = usageForStreamType(streamType); return this; } diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index a56e7f583094..f304f7cc5981 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; +import android.os.SystemProperties; import android.util.Log; import android.util.Pair; import android.util.Range; @@ -1105,7 +1106,11 @@ public final class MediaCodecInfo { mBitrateRange = Range.create(0, Integer.MAX_VALUE); mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT; // mBitrateRange = Range.create(1, 320000); - mSampleRateRanges = new Range[] { Range.create(8000, 96000) }; + final int minSampleRate = SystemProperties. + getInt("ro.mediacodec.min_sample_rate", 7350); + final int maxSampleRate = SystemProperties. + getInt("ro.mediacodec.max_sample_rate", 192000); + mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) }; mSampleRates = null; } @@ -1677,6 +1682,13 @@ public final class MediaCodecInfo { return "PerformancePoint(" + info + ")"; } + @Override + public int hashCode() { + // only max frame rate must equal between performance points that equal to one + // another + return mMaxFrameRate; + } + /** * Create a detailed performance point with custom max frame rate and macroblock size. * diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index 21b194d73ff9..5de56c718570 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -244,30 +244,77 @@ public class ThumbnailUtils { final Resizer resizer = new Resizer(size, signal); final String mimeType = MediaFile.getMimeTypeForFile(file.getName()); + Bitmap bitmap = null; + ExifInterface exif = null; + int orientation = 0; + + // get orientation + if (MediaFile.isExifMimeType(mimeType)) { + exif = new ExifInterface(file); + switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) { + case ExifInterface.ORIENTATION_ROTATE_90: + orientation = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + orientation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + orientation = 270; + break; + } + } + + boolean isHeifFile = false; + if (mimeType.equals("image/heif") || mimeType.equals("image/heif-sequence") || mimeType.equals("image/heic") || mimeType.equals("image/heic-sequence")) { + isHeifFile = true; try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { retriever.setDataSource(file.getAbsolutePath()); - return retriever.getThumbnailImageAtIndex(-1, + bitmap = retriever.getThumbnailImageAtIndex(-1, new MediaMetadataRetriever.BitmapParams(), size.getWidth(), size.getWidth() * size.getHeight()); } catch (RuntimeException e) { throw new IOException("Failed to create thumbnail", e); } - } else if (MediaFile.isExifMimeType(mimeType)) { - final ExifInterface exif = new ExifInterface(file); + } + + if (bitmap == null && exif != null) { final byte[] raw = exif.getThumbnailBytes(); if (raw != null) { - return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + try { + bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + } catch (ImageDecoder.DecodeException e) { + Log.w(TAG, e); + } } } // Checkpoint before going deeper if (signal != null) signal.throwIfCanceled(); - return ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer); + if (bitmap == null) { + bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer); + // Use ImageDecoder to do full image decode of heif format file + // will have right orientation. Don't rotate the bitmap again. + if (isHeifFile) { + return bitmap; + } + } + + // Transform the bitmap if the orientation of the image is not 0. + if (orientation != 0 && bitmap != null) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + + final Matrix m = new Matrix(); + m.setRotate(orientation, width / 2, height / 2); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); + } + + return bitmap; } /** diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index b2baff5db75b..73c789516754 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -23,6 +23,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityThread; import android.app.INotificationManager; import android.app.Notification; @@ -71,6 +72,7 @@ import java.util.concurrent.Executors; /** * Notification assistant that provides guidance on notification channel blocking */ +@SuppressLint("OverrideAbstract") public class Assistant extends NotificationAssistantService { private static final String TAG = "ExtAssistant"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -238,7 +240,7 @@ public class Assistant extends NotificationAssistantService { } mSingleThreadExecutor.submit(() -> { NotificationEntry entry = - new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + new NotificationEntry(getContext(), mPackageManager, sbn, channel, mSmsHelper); SmartActionsHelper.SmartSuggestions suggestions = mSmartActionsHelper.suggest(entry); if (DEBUG) { Log.d(TAG, String.format( @@ -295,7 +297,7 @@ public class Assistant extends NotificationAssistantService { } Ranking ranking = getRanking(sbn.getKey(), rankingMap); if (ranking != null && ranking.getChannel() != null) { - NotificationEntry entry = new NotificationEntry(mPackageManager, + NotificationEntry entry = new NotificationEntry(getContext(), mPackageManager, sbn, ranking.getChannel(), mSmsHelper); String key = getKey( sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId()); diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java index 84a8a8c577f7..1ffbac941dc3 100644 --- a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java +++ b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java @@ -28,18 +28,23 @@ import android.app.NotificationChannel; import android.app.Person; import android.app.RemoteInput; import android.content.ComponentName; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.graphics.drawable.Icon; import android.media.AudioAttributes; import android.media.AudioSystem; import android.os.Build; +import android.os.Parcelable; import android.os.RemoteException; import android.service.notification.StatusBarNotification; import android.util.Log; +import android.util.SparseArray; import java.util.ArrayList; import java.util.Objects; +import java.util.Set; /** * Holds data about notifications. @@ -47,6 +52,10 @@ import java.util.Objects; public class NotificationEntry { static final String TAG = "NotificationEntry"; + // Copied from hidden definitions in Notification.TvExtender + private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; + + private final Context mContext; private final StatusBarNotification mSbn; private final IPackageManager mPackageManager; private int mTargetSdkVersion = Build.VERSION_CODES.N_MR1; @@ -60,9 +69,10 @@ public class NotificationEntry { private final Object mLock = new Object(); - public NotificationEntry(IPackageManager packageManager, StatusBarNotification sbn, - NotificationChannel channel, SmsHelper smsHelper) { - mSbn = sbn; + public NotificationEntry(Context applicationContext, IPackageManager packageManager, + StatusBarNotification sbn, NotificationChannel channel, SmsHelper smsHelper) { + mContext = applicationContext; + mSbn = cloneStatusBarNotificationLight(sbn); mChannel = channel; mPackageManager = packageManager; mPreChannelsNotification = isPreChannelsNotification(); @@ -71,6 +81,66 @@ public class NotificationEntry { mSmsHelper = smsHelper; } + /** Adapted from {@code Notification.lightenPayload}. */ + @SuppressWarnings("nullness") + private static void lightenNotificationPayload(Notification notification) { + notification.tickerView = null; + notification.contentView = null; + notification.bigContentView = null; + notification.headsUpContentView = null; + notification.largeIcon = null; + if (notification.extras != null && !notification.extras.isEmpty()) { + final Set<String> keyset = notification.extras.keySet(); + final int keysetSize = keyset.size(); + final String[] keys = keyset.toArray(new String[keysetSize]); + for (int i = 0; i < keysetSize; i++) { + final String key = keys[i]; + if (EXTRA_TV_EXTENDER.equals(key) + || Notification.EXTRA_MESSAGES.equals(key) + || Notification.EXTRA_MESSAGING_PERSON.equals(key) + || Notification.EXTRA_PEOPLE_LIST.equals(key)) { + continue; + } + final Object obj = notification.extras.get(key); + if (obj != null + && (obj instanceof Parcelable + || obj instanceof Parcelable[] + || obj instanceof SparseArray + || obj instanceof ArrayList)) { + notification.extras.remove(key); + } + } + } + } + + /** An interpretation of {@code Notification.cloneInto} with heavy=false. */ + private Notification cloneNotificationLight(Notification notification) { + // We can't just use clone() here because the only way to remove the icons is with the + // builder, which we can only create with a Context. + Notification lightNotification = + Notification.Builder.recoverBuilder(mContext, notification) + .setSmallIcon(0) + .setLargeIcon((Icon) null) + .build(); + lightenNotificationPayload(lightNotification); + return lightNotification; + } + + /** Adapted from {@code StatusBarNotification.cloneLight}. */ + public StatusBarNotification cloneStatusBarNotificationLight(StatusBarNotification sbn) { + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), + sbn.getTag(), + sbn.getUid(), + /*initialPid=*/ 0, + /*score=*/ 0, + cloneNotificationLight(sbn.getNotification()), + sbn.getUser(), + sbn.getPostTime()); + } + private boolean isPreChannelsNotification() { try { ApplicationInfo info = mPackageManager.getApplicationInfo( diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AgingHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AgingHelperTest.java index 3db275acfd06..a87d57cf872b 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/AgingHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/AgingHelperTest.java @@ -102,7 +102,8 @@ public class AgingHelperTest { public void testNoSnoozingOnPost() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); StatusBarNotification sbn = generateSbn(channel.getId()); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); mAgingHelper.onNotificationPosted(entry); @@ -113,7 +114,8 @@ public class AgingHelperTest { public void testPostResetsSnooze() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); StatusBarNotification sbn = generateSbn(channel.getId()); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); mAgingHelper.onNotificationPosted(entry); @@ -124,7 +126,8 @@ public class AgingHelperTest { public void testSnoozingOnSeen() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); StatusBarNotification sbn = generateSbn(channel.getId()); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); entry.setSeen(); when(mCategorizer.getCategory(entry)).thenReturn(NotificationCategorizer.CATEGORY_PEOPLE); @@ -137,7 +140,8 @@ public class AgingHelperTest { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); StatusBarNotification sbn = generateSbn(channel.getId()); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); when(mCategorizer.getCategory(entry)).thenReturn(NotificationCategorizer.CATEGORY_PEOPLE); mAgingHelper.onNotificationSeen(entry); diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java index ee29bc594801..012dcc01fe6f 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java @@ -468,8 +468,10 @@ public class AssistantTest extends ServiceTestCase<Assistant> { @Test public void testAssistantNeverIncreasesImportanceWhenSuggestingSilent() throws Exception { StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C3, "min notif!", null); - Adjustment adjust = mAssistant.createEnqueuedNotificationAdjustment(new NotificationEntry( - mPackageManager, sbn, P1C3, mSmsHelper), new ArrayList<>(), new ArrayList<>()); + Adjustment adjust = mAssistant.createEnqueuedNotificationAdjustment( + new NotificationEntry(mContext, mPackageManager, sbn, P1C3, mSmsHelper), + new ArrayList<>(), + new ArrayList<>()); assertEquals(IMPORTANCE_MIN, adjust.getSignals().getInt(Adjustment.KEY_IMPORTANCE)); } } diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/NotificationEntryTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/NotificationEntryTest.java index f51e911cbe97..c02607927b1b 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/NotificationEntryTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/NotificationEntryTest.java @@ -24,6 +24,7 @@ import static android.media.AudioAttributes.USAGE_ALARM; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -34,6 +35,8 @@ import android.app.Person; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.Icon; import android.media.AudioAttributes; import android.os.Build; import android.os.Process; @@ -41,9 +44,6 @@ import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.TestableContext; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -53,6 +53,9 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + @RunWith(AndroidJUnit4.class) public class NotificationEntryTest { private String mPkg = "pkg"; @@ -113,7 +116,8 @@ public class NotificationEntryTest { people.add(new Person.Builder().setKey("mailto:testing@android.com").build()); sbn.getNotification().extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, people); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); assertTrue(entry.involvesPeople()); } @@ -121,7 +125,8 @@ public class NotificationEntryTest { public void testNotPerson() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); StatusBarNotification sbn = generateSbn(channel.getId()); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); assertFalse(entry.involvesPeople()); } @@ -129,7 +134,8 @@ public class NotificationEntryTest { public void testHasPerson_matchesDefaultSmsApp() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); StatusBarNotification sbn = generateSbn(channel.getId(), DEFAULT_SMS_PACKAGE_NAME); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); assertTrue(entry.involvesPeople()); } @@ -137,7 +143,8 @@ public class NotificationEntryTest { public void testHasPerson_doesntMatchDefaultSmsApp() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); StatusBarNotification sbn = generateSbn(channel.getId(), "abc"); - NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, sbn, channel, mSmsHelper); assertFalse(entry.involvesPeople()); } @@ -148,8 +155,8 @@ public class NotificationEntryTest { Notification n = new Notification.Builder(mContext, channel.getId()) .setStyle(new Notification.InboxStyle()) .build(); - NotificationEntry entry = - new NotificationEntry(mPackageManager, generateSbn(n), channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); assertTrue(entry.hasStyle(Notification.InboxStyle.class)); } @@ -160,8 +167,8 @@ public class NotificationEntryTest { Notification n = new Notification.Builder(mContext, channel.getId()) .setStyle(new Notification.MessagingStyle("")) .build(); - NotificationEntry entry = - new NotificationEntry(mPackageManager, generateSbn(n), channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); assertTrue(entry.hasStyle(Notification.MessagingStyle.class)); } @@ -172,8 +179,8 @@ public class NotificationEntryTest { Notification n = new Notification.Builder(mContext, channel.getId()) .setStyle(new Notification.BigPictureStyle()) .build(); - NotificationEntry entry = - new NotificationEntry(mPackageManager, generateSbn(n), channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); assertFalse(entry.hasStyle(Notification.InboxStyle.class)); assertFalse(entry.hasStyle(Notification.MessagingStyle.class)); } @@ -184,7 +191,7 @@ public class NotificationEntryTest { channel.setSound(null, new AudioAttributes.Builder().setUsage(USAGE_ALARM).build()); NotificationEntry entry = new NotificationEntry( - mPackageManager, generateSbn(channel.getId()), channel, mSmsHelper); + mContext, mPackageManager, generateSbn(channel.getId()), channel, mSmsHelper); assertTrue(entry.isAudioAttributesUsage(USAGE_ALARM)); } @@ -193,7 +200,7 @@ public class NotificationEntryTest { public void testIsNotAudioAttributes() { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); NotificationEntry entry = new NotificationEntry( - mPackageManager, generateSbn(channel.getId()), channel, mSmsHelper); + mContext, mPackageManager, generateSbn(channel.getId()), channel, mSmsHelper); assertFalse(entry.isAudioAttributesUsage(USAGE_ALARM)); } @@ -205,8 +212,8 @@ public class NotificationEntryTest { Notification n = new Notification.Builder(mContext, channel.getId()) .setCategory(Notification.CATEGORY_EMAIL) .build(); - NotificationEntry entry = - new NotificationEntry(mPackageManager, generateSbn(n), channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); assertTrue(entry.isCategory(Notification.CATEGORY_EMAIL)); assertFalse(entry.isCategory(Notification.CATEGORY_MESSAGE)); @@ -219,8 +226,8 @@ public class NotificationEntryTest { Notification n = new Notification.Builder(mContext, channel.getId()) .setFlag(FLAG_FOREGROUND_SERVICE, true) .build(); - NotificationEntry entry = - new NotificationEntry(mPackageManager, generateSbn(n), channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); assertTrue(entry.isOngoing()); } @@ -232,9 +239,28 @@ public class NotificationEntryTest { Notification n = new Notification.Builder(mContext, channel.getId()) .setFlag(FLAG_CAN_COLORIZE, true) .build(); - NotificationEntry entry = - new NotificationEntry(mPackageManager, generateSbn(n), channel, mSmsHelper); + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); assertFalse(entry.isOngoing()); } + + @Test + public void testShrinkNotification() { + Notification n = new Notification.Builder(mContext, "") + .setLargeIcon(Icon.createWithResource( + mContext, android.R.drawable.alert_dark_frame)) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + n.largeIcon = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); + + NotificationEntry entry = new NotificationEntry( + mContext, mPackageManager, generateSbn(n), channel, mSmsHelper); + + assertNull(entry.getNotification().getSmallIcon()); + assertNull(entry.getNotification().getLargeIcon()); + assertNull(entry.getNotification().largeIcon); + assertNull(entry.getNotification().extras.getParcelable(Notification.EXTRA_LARGE_ICON)); + } } diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java index dfa1ea0aac90..69abe87cd429 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java @@ -46,9 +46,6 @@ import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextClassifierEvent; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - import com.google.common.truth.FailureStrategy; import com.google.common.truth.Subject; import com.google.common.truth.SubjectFactory; @@ -71,9 +68,11 @@ import java.util.Objects; import javax.annotation.Nullable; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + @RunWith(AndroidJUnit4.class) public class SmartActionsHelperTest { - private static final String NOTIFICATION_KEY = "key"; private static final String RESULT_ID = "id"; private static final float SCORE = 0.7f; private static final CharSequence SMART_REPLY = "Home"; @@ -88,7 +87,6 @@ public class SmartActionsHelperTest { IPackageManager mIPackageManager; @Mock private TextClassifier mTextClassifier; - @Mock private StatusBarNotification mStatusBarNotification; @Mock private SmsHelper mSmsHelper; @@ -108,9 +106,6 @@ public class SmartActionsHelperTest { when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) .thenReturn(new ConversationActions(Arrays.asList(REPLY_ACTION), RESULT_ID)); - when(mStatusBarNotification.getPackageName()).thenReturn("random.app"); - when(mStatusBarNotification.getUser()).thenReturn(Process.myUserHandle()); - when(mStatusBarNotification.getKey()).thenReturn(NOTIFICATION_KEY); mNotificationBuilder = new Notification.Builder(mContext, "channel"); mSettings = AssistantSettings.createForTesting( null, null, Process.myUserHandle().getIdentifier(), null); @@ -119,10 +114,15 @@ public class SmartActionsHelperTest { mSmartActionsHelper = new SmartActionsHelper(mContext, mSettings); } + private void setStatusBarNotification(Notification n) { + mStatusBarNotification = new StatusBarNotification("random.app", "random.app", 0, + "tag", Process.myUid(), Process.myPid(), n, Process.myUserHandle(), null, 0); + } + @Test public void testSuggest_notMessageNotification() { Notification notification = mNotificationBuilder.setContentText(MESSAGE).build(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); @@ -137,7 +137,7 @@ public class SmartActionsHelperTest { .setContentText(MESSAGE) .setCategory(Notification.CATEGORY_MESSAGE) .build(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); ConversationActions.Request request = runSuggestAndCaptureRequest(); @@ -154,7 +154,7 @@ public class SmartActionsHelperTest { mSettings.mGenerateActions = false; mSettings.mGenerateReplies = false; Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); @@ -167,7 +167,7 @@ public class SmartActionsHelperTest { mSettings.mGenerateReplies = true; mSettings.mGenerateActions = false; Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); ConversationActions.Request request = runSuggestAndCaptureRequest(); @@ -184,7 +184,7 @@ public class SmartActionsHelperTest { mSettings.mGenerateReplies = false; mSettings.mGenerateActions = true; Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); ConversationActions.Request request = runSuggestAndCaptureRequest(); @@ -200,7 +200,7 @@ public class SmartActionsHelperTest { @Test public void testSuggest_nonMessageStyleMessageNotification() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); List<ConversationActions.Message> messages = runSuggestAndCaptureRequest().getConversation(); @@ -233,7 +233,7 @@ public class SmartActionsHelperTest { .setStyle(style) .setActions(createReplyAction()) .build(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); List<ConversationActions.Message> messages = runSuggestAndCaptureRequest().getConversation(); @@ -288,7 +288,7 @@ public class SmartActionsHelperTest { .setStyle(style) .setActions(createReplyAction()) .build(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); @@ -307,7 +307,7 @@ public class SmartActionsHelperTest { .setStyle(style) .setActions(createReplyAction()) .build(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); @@ -318,11 +318,11 @@ public class SmartActionsHelperTest { @Test public void testOnSuggestedReplySent() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); - mSmartActionsHelper.onSuggestedReplySent( - NOTIFICATION_KEY, SMART_REPLY, NotificationAssistantService.SOURCE_FROM_ASSISTANT); + mSmartActionsHelper.onSuggestedReplySent(mStatusBarNotification.getKey(), SMART_REPLY, + NotificationAssistantService.SOURCE_FROM_ASSISTANT); ArgumentCaptor<TextClassifierEvent> argumentCaptor = ArgumentCaptor.forClass(TextClassifierEvent.class); @@ -338,7 +338,7 @@ public class SmartActionsHelperTest { @Test public void testOnSuggestedReplySent_anotherNotification() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); mSmartActionsHelper.onSuggestedReplySent( @@ -353,11 +353,11 @@ public class SmartActionsHelperTest { when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) .thenReturn(new ConversationActions(Collections.singletonList(REPLY_ACTION), null)); Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); - mSmartActionsHelper.onSuggestedReplySent( - NOTIFICATION_KEY, SMART_REPLY, NotificationAssistantService.SOURCE_FROM_ASSISTANT); + mSmartActionsHelper.onSuggestedReplySent(mStatusBarNotification.getKey(), SMART_REPLY, + NotificationAssistantService.SOURCE_FROM_ASSISTANT); verify(mTextClassifier, never()).onTextClassifierEvent(any(TextClassifierEvent.class)); } @@ -365,10 +365,10 @@ public class SmartActionsHelperTest { @Test public void testOnNotificationDirectReply() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); - mSmartActionsHelper.onNotificationDirectReplied(NOTIFICATION_KEY); + mSmartActionsHelper.onNotificationDirectReplied(mStatusBarNotification.getKey()); ArgumentCaptor<TextClassifierEvent> argumentCaptor = ArgumentCaptor.forClass(TextClassifierEvent.class); @@ -381,7 +381,7 @@ public class SmartActionsHelperTest { @Test public void testOnNotificationExpansionChanged() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), true); @@ -397,7 +397,7 @@ public class SmartActionsHelperTest { @Test public void testOnNotificationsSeen_notExpanded() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), false); @@ -409,7 +409,7 @@ public class SmartActionsHelperTest { @Test public void testOnNotifications_expanded() { Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); mSmartActionsHelper.suggest(createNotificationEntry()); mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), true); @@ -438,7 +438,7 @@ public class SmartActionsHelperTest { Collections.singletonList(conversationAction), null)); Notification notification = createMessageNotification(); - when(mStatusBarNotification.getNotification()).thenReturn(notification); + setStatusBarNotification(notification); SmartActionsHelper.SmartSuggestions suggestions = mSmartActionsHelper.suggest(createNotificationEntry()); @@ -477,7 +477,8 @@ public class SmartActionsHelperTest { private NotificationEntry createNotificationEntry() { NotificationChannel channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); - return new NotificationEntry(mIPackageManager, mStatusBarNotification, channel, mSmsHelper); + return new NotificationEntry( + mContext, mIPackageManager, mStatusBarNotification, channel, mSmsHelper); } private Notification createMessageNotification() { diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index 0ffd471e64af..05c2f24001e8 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -174,7 +174,7 @@ public class Tile implements Parcelable { packageManager.getResourcesForApplication(mActivityPackage); title = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_TITLE)); } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { - Log.d(TAG, "Couldn't find info", e); + Log.w(TAG, "Couldn't find info", e); } } else { title = mMetaData.getString(META_DATA_PREFERENCE_TITLE); @@ -183,26 +183,16 @@ public class Tile implements Parcelable { // Set the preference title to the activity's label if no // meta-data is found if (title == null) { - title = getActivityInfo(context).loadLabel(packageManager); + final ActivityInfo activityInfo = getActivityInfo(context); + if (activityInfo == null) { + return null; + } + title = activityInfo.loadLabel(packageManager); } return title; } /** - * Returns the raw metadata for summary, this is used for comparing 2 summary text without - * loading the real string. - */ - public String getSummaryReference() { - if (mSummaryOverride != null) { - return mSummaryOverride.toString(); - } - if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) { - return mMetaData.get(META_DATA_PREFERENCE_SUMMARY).toString(); - } - return null; - } - - /** * Overrides the summary. This can happen when injected tile wants to provide dynamic summary. */ public void overrideSummary(CharSequence summaryOverride) { @@ -302,7 +292,7 @@ public class Tile implements Parcelable { if (iconResId != 0) { final Icon icon = Icon.createWithResource(activityInfo.packageName, iconResId); if (isIconTintable(context)) { - final TypedArray a = context.obtainStyledAttributes(new int[] { + final TypedArray a = context.obtainStyledAttributes(new int[]{ android.R.attr.colorControlNormal}); final int tintColor = a.getColor(0, 0); a.recycle(); @@ -357,6 +347,9 @@ public class Tile implements Parcelable { if (infoList != null && !infoList.isEmpty()) { mActivityInfo = infoList.get(0).activityInfo; mMetaData = mActivityInfo.metaData; + } else { + Log.e(TAG, "Cannot find package info for " + + intent.getComponent().flattenToString()); } } return mActivityInfo; diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt index 257943e16149..a5b5312707d0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt +++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt @@ -44,6 +44,8 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int) // doesn't touch the walls private val perimeterPath = Path() private val scaledPerimeter = Path() + private val errorPerimeterPath = Path() + private val scaledErrorPerimeter = Path() // Fill will cover the whole bounding rect of the fillMask, and be masked by the path private val fillMask = Path() private val scaledFill = Path() @@ -242,7 +244,7 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int) } } else if (powerSaveEnabled) { // If power save is enabled draw the perimeter path with colorError - c.drawPath(scaledPerimeter, errorPaint) + c.drawPath(scaledErrorPerimeter, errorPaint) // And draw the plus sign on top of the fill c.drawPath(scaledPlus, errorPaint) } @@ -364,6 +366,7 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int) } perimeterPath.transform(scaleMatrix, scaledPerimeter) + errorPerimeterPath.transform(scaleMatrix, scaledErrorPerimeter) fillMask.transform(scaleMatrix, scaledFill) scaledFill.computeBounds(fillRect, true) boltPath.transform(scaleMatrix, scaledBolt) @@ -382,8 +385,12 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int) val pathString = context.resources.getString( com.android.internal.R.string.config_batterymeterPerimeterPath) perimeterPath.set(PathParser.createPathFromPathData(pathString)) - val b = RectF() - perimeterPath.computeBounds(b, true) + perimeterPath.computeBounds(RectF(), true) + + val errorPathString = context.resources.getString( + com.android.internal.R.string.config_batterymeterErrorPerimeterPath) + errorPerimeterPath.set(PathParser.createPathFromPathData(errorPathString)) + errorPerimeterPath.computeBounds(RectF(), true) val fillMaskString = context.resources.getString( com.android.internal.R.string.config_batterymeterFillMask) diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 5f2bc4e1d490..d8172a0f9430 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -123,7 +123,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro /** * Synchronization lock for managing concurrency between main and worker threads. * - * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}. + * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints} and + * {@link #mScanner}. */ private final Object mLock = new Object(); @@ -168,6 +169,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private static final String WIFI_SECURITY_OWE = "OWE"; private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192"; + @GuardedBy("mLock") @VisibleForTesting Scanner mScanner; @@ -276,9 +278,11 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * <p>Sets {@link #mStaleScanResults} to true. */ private void pauseScanning() { - if (mScanner != null) { - mScanner.pause(); - mScanner = null; + synchronized (mLock) { + if (mScanner != null) { + mScanner.pause(); + mScanner = null; + } } mStaleScanResults = true; } @@ -289,12 +293,14 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * <p>The score cache should be registered before this method is invoked. */ public void resumeScanning() { - if (mScanner == null) { - mScanner = new Scanner(); - } + synchronized (mLock) { + if (mScanner == null) { + mScanner = new Scanner(); + } - if (isWifiEnabled()) { - mScanner.resume(); + if (isWifiEnabled()) { + mScanner.resume(); + } } } @@ -743,7 +749,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } private void updateNetworkInfo(NetworkInfo networkInfo) { - /* Sticky broadcasts can call this when wifi is disabled */ if (!isWifiEnabled()) { clearAccessPointsAndConditionallyUpdate(); @@ -880,18 +885,25 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * true. */ private void updateWifiState(int state) { + if (isVerboseLoggingEnabled()) { + Log.d(TAG, "updateWifiState: " + state); + } if (state == WifiManager.WIFI_STATE_ENABLED) { - if (mScanner != null) { - // We only need to resume if mScanner isn't null because - // that means we want to be scanning. - mScanner.resume(); + synchronized (mLock) { + if (mScanner != null) { + // We only need to resume if mScanner isn't null because + // that means we want to be scanning. + mScanner.resume(); + } } } else { clearAccessPointsAndConditionallyUpdate(); mLastInfo = null; mLastNetworkInfo = null; - if (mScanner != null) { - mScanner.pause(); + synchronized (mLock) { + if (mScanner != null) { + mScanner.pause(); + } } mStaleScanResults = true; } @@ -919,12 +931,18 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private int mRetry = 0; void resume() { + if (isVerboseLoggingEnabled()) { + Log.d(TAG, "Scanner resume"); + } if (!hasMessages(MSG_SCAN)) { sendEmptyMessage(MSG_SCAN); } } void pause() { + if (isVerboseLoggingEnabled()) { + Log.d(TAG, "Scanner pause"); + } mRetry = 0; removeMessages(MSG_SCAN); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java index d0d1e58effaa..5da6205d45bd 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java @@ -23,6 +23,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowPackageManager; +import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) public class TileTest { @@ -164,4 +165,17 @@ public class TileTest { assertThat(tile.mLastUpdateTime).isNotEqualTo(staleTimeStamp); } + + @Test + public void getTitle_noActivity_returnNull() { + final ResolveInfo info = new ResolveInfo(); + info.activityInfo = mActivityInfo; + final ShadowPackageManager spm = Shadow.extract(mContext.getPackageManager()); + spm.removePackage(mActivityInfo.packageName); + + final Tile tile = new Tile(mActivityInfo, "category"); + ReflectionHelpers.setField(tile, "mActivityInfo", null); + + assertThat(tile.getTitle(RuntimeEnvironment.application)).isNull(); + } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java index 6be770788ce1..68d2ed7d4689 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java @@ -26,8 +26,8 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; * Plugin which provides a "Panel" {@link View} to be rendered inside of the GlobalActions menu. * * Implementations should construct a new {@link PanelViewController} with the given - * {@link Callbacks} instance inside of {@link #onPanelShown(Callbacks)}, and should not hold onto - * a reference, instead allowing Global Actions to manage the lifetime of the object. + * {@link Callbacks} instance inside of {@link #onPanelShown(Callbacks, boolean)}, and should not + * hold onto a reference, instead allowing Global Actions to manage the lifetime of the object. * * Under this assumption, {@link PanelViewController} represents the lifetime of a single invocation * of the Global Actions menu. The {@link View} for the Panel is generated when the @@ -50,9 +50,10 @@ public interface GlobalActionsPanelPlugin extends Plugin { * * @param callbacks {@link Callbacks} instance that can be used by the Panel to interact with * the Global Actions menu. + * @param deviceLocked Indicates whether or not the device is currently locked. * @return A {@link PanelViewController} instance used to receive Global Actions events. */ - PanelViewController onPanelShown(Callbacks callbacks); + PanelViewController onPanelShown(Callbacks callbacks, boolean deviceLocked); /** * Provides methods to interact with the Global Actions menu. @@ -92,5 +93,10 @@ public interface GlobalActionsPanelPlugin extends Plugin { * {@link #getPanelContent()}) is dismissed. */ void onDismissed(); + + /** + * Invoked when the device is either locked or unlocked. + */ + void onDeviceLockStateChanged(boolean locked); } } diff --git a/packages/SystemUI/res/layout-land/global_actions_column.xml b/packages/SystemUI/res/layout-land/global_actions_column.xml new file mode 100644 index 000000000000..99a4e13d2064 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_column.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<com.android.systemui.globalactions.GlobalActionsColumnLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="center_horizontal | top" + android:clipChildren="false" +> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:padding="0dp" + android:orientation="horizontal" + android:clipChildren="false" + android:clipToPadding="false" + > + <!-- Grid of action items --> + <LinearLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:background="?android:attr/colorBackgroundFloating" + /> + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="horizontal" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsColumnLayout> diff --git a/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml new file mode 100644 index 000000000000..0f8613194959 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<com.android.systemui.globalactions.GlobalActionsColumnLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="center_horizontal | bottom" + android:clipChildren="false" +> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:padding="0dp" + android:orientation="horizontal" + > + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_gravity="bottom|left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:background="?android:attr/colorBackgroundFloating" + /> + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_gravity="top|left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="horizontal" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsColumnLayout> diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml index 65ede3def821..b73412526f5e 100644 --- a/packages/SystemUI/res/layout/bubble_expanded_view.xml +++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml @@ -33,13 +33,15 @@ android:layout_height="wrap_content" android:animateLayoutChanges="true"> - <ImageView + <com.android.systemui.statusbar.AlphaOptimizedButton + style="@android:style/Widget.Material.Button.Borderless" android:id="@+id/settings_button" - android:layout_width="@dimen/bubble_header_icon_size" - android:layout_height="@dimen/bubble_header_icon_size" - android:src="@drawable/ic_settings" - android:scaleType="center" - android:layout_gravity="end"/> + android:layout_gravity="end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:text="@string/manage_bubbles_text" + android:textColor="?attr/wallpaperTextColor"/> <include layout="@layout/bubble_permission_view" android:id="@+id/permission_layout" diff --git a/packages/SystemUI/res/layout/global_actions_column.xml b/packages/SystemUI/res/layout/global_actions_column.xml new file mode 100644 index 000000000000..b58146b6ce41 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_column.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<com.android.systemui.globalactions.GlobalActionsColumnLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="center_vertical | right" + android:clipChildren="false" +> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="top | right" + android:orientation="vertical" + android:padding="0dp" + android:layout_marginTop="@dimen/global_actions_grid_container_bottom_margin" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- Global actions is right-aligned to be physically near power button --> + <LinearLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="right" + android:translationZ="@dimen/global_actions_translate" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + /> + + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="vertical" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" + /> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsColumnLayout> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index cea336c82327..dc35653e5f7d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2414,6 +2414,8 @@ <!-- Text for asking the user whether bubbles (floating app content) should be enabled for an app. [CHAR LIMIT=NONE] --> <string name="bubbles_prompt">Allow bubbles from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string> + <!-- The text for the manage bubbles link. [CHAR LIMIT=NONE] --> + <string name="manage_bubbles_text">Manage</string> <!-- Text used for button allowing user to opt out of bubbles [CHAR LIMIT=20] --> <string name="no_bubbles">Deny</string> <!-- Text used for button allowing user to approve / enable bubbles [CHAR LIMIT=20] --> diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java index 802903db7ba8..ad2e0024065f 100644 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java @@ -80,16 +80,6 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { } @Override - public void removeAllItems() { - if (mList != null) { - mList.removeAllViews(); - } - if (mSeparatedView != null) { - mSeparatedView.removeAllViews(); - } - } - - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); updateSettings(); diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index f8287a4dc2dc..d153fb039802 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -45,11 +45,6 @@ public abstract class MultiListLayout extends LinearLayout { protected abstract ViewGroup getListView(); /** - * Removes all child items from the separated and list views, if they exist. - */ - protected abstract void removeAllItems(); - - /** * Sets the divided view, which may have a differently-colored background. */ public abstract void setDivisionView(View v); @@ -110,6 +105,25 @@ public abstract class MultiListLayout extends LinearLayout { onUpdateList(); } + protected void removeAllSeparatedViews() { + ViewGroup separated = getSeparatedView(); + if (separated != null) { + separated.removeAllViews(); + } + } + + protected void removeAllListViews() { + ViewGroup list = getListView(); + if (list != null) { + list.removeAllViews(); + } + } + + protected void removeAllItems() { + removeAllListViews(); + removeAllSeparatedViews(); + } + protected void onUpdateList() { removeAllItems(); setSeparatedViewVisibility(mAdapter.hasSeparatedItems()); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 346660df7fb5..67f327490a6e 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -40,11 +40,8 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Point; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; import android.os.ServiceManager; @@ -66,6 +63,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; +import com.android.systemui.statusbar.AlphaOptimizedButton; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; @@ -80,7 +78,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private View mPointerView; private int mPointerMargin; - private ImageView mSettingsIcon; + private AlphaOptimizedButton mSettingsIcon; // Permission view private View mPermissionView; @@ -100,8 +98,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private int mSettingsIconHeight; private int mBubbleHeight; private int mPermissionHeight; - private int mIconInset; - private Drawable mSettingsIconDrawable; private int mPointerWidth; private int mPointerHeight; @@ -218,10 +214,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mSettingsIconHeight = getContext().getResources().getDimensionPixelSize( R.dimen.bubble_expanded_header_height); mSettingsIcon = findViewById(R.id.settings_button); - mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset); mSettingsIcon.setOnClickListener(this); - // Save initial drawable to create adaptive icons that will take its place. - mSettingsIconDrawable = mSettingsIcon.getDrawable(); mPermissionHeight = getContext().getResources().getDimensionPixelSize( R.dimen.bubble_permission_height); @@ -377,16 +370,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList int foregroundColor = ta.getColor(1, Color.BLACK /* default */); ta.recycle(); - // Must clear tint first - otherwise tint updates inconsistently. - mSettingsIconDrawable.setTintList(null); - mSettingsIconDrawable.setTint(foregroundColor); - - InsetDrawable foreground = new InsetDrawable(mSettingsIconDrawable, mIconInset); - ColorDrawable background = new ColorDrawable(backgroundColor); - AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(background, - foreground); - mSettingsIcon.setImageDrawable(adaptiveIcon); - // Update permission prompt color. mPermissionView.setBackground(createPermissionBackground(backgroundColor)); mPermissionPrompt.setTextColor(foregroundColor); @@ -649,11 +632,12 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } private Intent getSettingsIntent(String packageName, final int appUid) { - final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); intent.putExtra(Settings.EXTRA_APP_UID, appUid); intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java new file mode 100644 index 000000000000..59070287cc5c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsColumnLayout extends GlobalActionsLayout { + private boolean mLastSnap; + + public GlobalActionsColumnLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + post(() -> updateSnap()); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @VisibleForTesting + protected boolean shouldReverseListItems() { + int rotation = getCurrentRotation(); + if (rotation == ROTATION_NONE) { + return false; + } + if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + return rotation == ROTATION_LANDSCAPE; + } + return rotation == ROTATION_SEASCAPE; + } + + @Override + public void onUpdateList() { + super.onUpdateList(); + updateChildOrdering(); + } + + private void updateChildOrdering() { + if (shouldReverseListItems()) { + getListView().bringToFront(); + } else { + getSeparatedView().bringToFront(); + } + } + + /** + * Snap this layout to align with the power button. + */ + @VisibleForTesting + protected void snapToPowerButton() { + int offset = getPowerButtonOffsetDistance(); + switch (getCurrentRotation()) { + case (ROTATION_LANDSCAPE): + setPadding(offset, 0, 0, 0); + setGravity(Gravity.LEFT | Gravity.TOP); + break; + case (ROTATION_SEASCAPE): + setPadding(0, 0, offset, 0); + setGravity(Gravity.RIGHT | Gravity.BOTTOM); + break; + default: + setPadding(0, offset, 0, 0); + setGravity(Gravity.TOP | Gravity.RIGHT); + break; + } + } + + /** + * Detach this layout from snapping to the power button and instead center along that edge. + */ + @VisibleForTesting + protected void centerAlongEdge() { + switch (getCurrentRotation()) { + case (ROTATION_LANDSCAPE): + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); + break; + case (ROTATION_SEASCAPE): + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + break; + default: + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + break; + } + } + + /** + * Determines the distance from the top of the screen to the power button. + */ + @VisibleForTesting + protected int getPowerButtonOffsetDistance() { + return Math.round(getContext().getResources().getDimension( + R.dimen.global_actions_top_padding)); + } + + /** + * Check whether there is enough extra space below the dialog such that we can offset the top + * of the dialog from the top of the phone to line it up with the power button, then either + * snap the dialog to the power button or center it along the edge with snapToPowerButton. + */ + @VisibleForTesting + protected boolean shouldSnapToPowerButton() { + int offsetSize = getPowerButtonOffsetDistance(); + int dialogSize; + int screenSize; + View wrapper = getWrapper(); + int rotation = getCurrentRotation(); + if (rotation == ROTATION_NONE) { + dialogSize = wrapper.getMeasuredHeight(); + screenSize = getMeasuredHeight(); + } else { + dialogSize = wrapper.getMeasuredWidth(); + screenSize = getMeasuredWidth(); + } + return dialogSize + offsetSize < screenSize; + } + + @VisibleForTesting + protected void updateSnap() { + boolean snap = shouldSnapToPowerButton(); + if (snap != mLastSnap) { + if (snap) { + snapToPowerButton(); + } else { + centerAlongEdge(); + } + } + mLastSnap = snap; + } + + @VisibleForTesting + protected float getGridItemSize() { + return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height); + } + + @VisibleForTesting + protected float getAnimationDistance() { + return getGridItemSize() / 2; + } + + @Override + public float getAnimationOffsetX() { + if (getCurrentRotation() == ROTATION_NONE) { + return getAnimationDistance(); + } + return 0; + } + + @Override + public float getAnimationOffsetY() { + switch (getCurrentRotation()) { + case ROTATION_LANDSCAPE: + return -getAnimationDistance(); + case ROTATION_SEASCAPE: + return getAnimationDistance(); + default: // Portrait + return 0; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 64511a0b4cc3..68d792e16274 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -410,7 +410,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mActivityStarter .startPendingIntentDismissingKeyguard(intent); } - }) + }, + mKeyguardManager.isDeviceLocked()) : null; ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. @@ -1583,13 +1584,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private int getGlobalActionsLayoutId(Context context) { - if (isForceGridEnabled(context) || shouldUsePanel()) { - if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) { + boolean useGridLayout = isForceGridEnabled(context) || shouldUsePanel(); + if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) { + if (useGridLayout) { return com.android.systemui.R.layout.global_actions_grid_seascape; + } else { + return com.android.systemui.R.layout.global_actions_column_seascape; + } + } else { + if (useGridLayout) { + return com.android.systemui.R.layout.global_actions_grid; + } else { + return com.android.systemui.R.layout.global_actions_column; } - return com.android.systemui.R.layout.global_actions_grid; } - return com.android.systemui.R.layout.global_actions_wrapped; } @Override @@ -1712,7 +1720,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } public void onRotate(int from, int to) { - if (mShowing && (shouldUsePanel() || isForceGridEnabled(mContext))) { + if (mShowing) { refreshDialog(); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 554ed738c335..e1462d15c887 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -22,58 +22,23 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.HardwareBgDrawable; -import com.android.systemui.MultiListLayout; -import com.android.systemui.util.leak.RotationUtils; /** * Grid-based implementation of the button layout created by the global actions dialog. */ -public class GlobalActionsGridLayout extends MultiListLayout { - - boolean mBackgroundsSet; - +public class GlobalActionsGridLayout extends GlobalActionsLayout { public GlobalActionsGridLayout(Context context, AttributeSet attrs) { super(context, attrs); } - private void setBackgrounds() { - int gridBackgroundColor = getResources().getColor( - com.android.systemui.R.color.global_actions_grid_background, null); - int separatedBackgroundColor = getResources().getColor( - com.android.systemui.R.color.global_actions_separated_background, null); - HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); - HardwareBgDrawable separatedBackground = new HardwareBgDrawable(true, true, getContext()); - listBackground.setTint(gridBackgroundColor); - separatedBackground.setTint(separatedBackgroundColor); - getListView().setBackground(listBackground); - getSeparatedView().setBackground(separatedBackground); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // backgrounds set only once, the first time onMeasure is called after inflation - if (getListView() != null && !mBackgroundsSet) { - setBackgrounds(); - mBackgroundsSet = true; - } - } - - @VisibleForTesting - protected int getCurrentRotation() { - return RotationUtils.getRotation(mContext); - } - @VisibleForTesting - protected void setupListView(ListGridLayout listView, int itemCount) { - listView.setExpectedCount(itemCount); + protected void setupListView() { + ListGridLayout listView = getListView(); + listView.setExpectedCount(mAdapter.countListItems()); listView.setReverseSublists(shouldReverseSublists()); listView.setReverseItems(shouldReverseListItems()); listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); @@ -81,29 +46,8 @@ public class GlobalActionsGridLayout extends MultiListLayout { @Override public void onUpdateList() { + setupListView(); super.onUpdateList(); - - ViewGroup separatedView = getSeparatedView(); - ListGridLayout listView = getListView(); - setupListView(listView, mAdapter.countListItems()); - - for (int i = 0; i < mAdapter.getCount(); i++) { - // generate the view item - View v; - boolean separated = mAdapter.shouldBeSeparated(i); - if (separated) { - v = mAdapter.getView(i, null, separatedView); - } else { - v = mAdapter.getView(i, null, listView); - } - Log.d("GlobalActionsGridLayout", "View: " + v); - - if (separated) { - separatedView.addView(v); - } else { - listView.addItem(v); - } - } updateSeparatedItemSize(); } @@ -111,7 +55,8 @@ public class GlobalActionsGridLayout extends MultiListLayout { * If the separated view contains only one item, expand the bounds of that item to take up the * entire view, so that the whole thing is touch-able. */ - private void updateSeparatedItemSize() { + @VisibleForTesting + protected void updateSeparatedItemSize() { ViewGroup separated = getSeparatedView(); if (separated.getChildCount() == 0) { return; @@ -129,13 +74,24 @@ public class GlobalActionsGridLayout extends MultiListLayout { } @Override - protected ViewGroup getSeparatedView() { - return findViewById(com.android.systemui.R.id.separated_button); + protected ListGridLayout getListView() { + return (ListGridLayout) super.getListView(); } @Override - protected ListGridLayout getListView() { - return findViewById(android.R.id.list); + protected void removeAllListViews() { + ListGridLayout list = getListView(); + if (list != null) { + list.removeAllItems(); + } + } + + @Override + protected void addToListView(View v, boolean reverse) { + ListGridLayout list = getListView(); + if (list != null) { + list.addItem(v); + } } @Override @@ -174,12 +130,7 @@ public class GlobalActionsGridLayout extends MultiListLayout { return true; } - /** - * Determines whether the ListGridLayout should reverse the ordering of items within sublists. - * Used for RTL languages to ensure that items appear in the same positions, without having to - * override layoutDirection, which breaks Talkback ordering. - */ - @VisibleForTesting + @Override protected boolean shouldReverseListItems() { int rotation = getCurrentRotation(); boolean reverse = false; // should we add items to parents in the reverse order? @@ -187,20 +138,13 @@ public class GlobalActionsGridLayout extends MultiListLayout { || rotation == ROTATION_SEASCAPE) { reverse = !reverse; // if we're in portrait or seascape, reverse items } - if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { reverse = !reverse; // if we're in an RTL language, reverse items (again) } return reverse; } - /** - * Not ued in this implementation of the Global Actions Menu, but necessary for some others. - */ - @Override - public void setDivisionView(View v) { - // do nothing - } - + @VisibleForTesting protected float getAnimationDistance() { int rows = getListView().getRowCount(); float gridItemSize = getContext().getResources().getDimension( diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java new file mode 100644 index 000000000000..f755a93ed275 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.HardwareBgDrawable; +import com.android.systemui.MultiListLayout; +import com.android.systemui.R; +import com.android.systemui.util.leak.RotationUtils; + +import java.util.Locale; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public abstract class GlobalActionsLayout extends MultiListLayout { + + boolean mBackgroundsSet; + + public GlobalActionsLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void setBackgrounds() { + int gridBackgroundColor = getResources().getColor( + R.color.global_actions_grid_background, null); + int separatedBackgroundColor = getResources().getColor( + R.color.global_actions_separated_background, null); + HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); + HardwareBgDrawable separatedBackground = new HardwareBgDrawable(true, true, getContext()); + listBackground.setTint(gridBackgroundColor); + separatedBackground.setTint(separatedBackgroundColor); + getListView().setBackground(listBackground); + getSeparatedView().setBackground(separatedBackground); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + if (getListView() != null && !mBackgroundsSet) { + setBackgrounds(); + mBackgroundsSet = true; + } + } + + protected void addToListView(View v, boolean reverse) { + if (reverse) { + getListView().addView(v, 0); + } else { + getListView().addView(v); + } + } + + protected void addToSeparatedView(View v, boolean reverse) { + if (reverse) { + getSeparatedView().addView(v, 0); + } else { + getSeparatedView().addView(v); + } + } + + @VisibleForTesting + protected int getCurrentLayoutDirection() { + return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); + } + + @VisibleForTesting + protected int getCurrentRotation() { + return RotationUtils.getRotation(mContext); + } + + /** + * Determines whether the ListGridLayout should reverse the ordering of items within sublists. + * Used for RTL languages to ensure that items appear in the same positions, without having to + * override layoutDirection, which breaks Talkback ordering. + */ + protected abstract boolean shouldReverseListItems(); + + @Override + public void onUpdateList() { + super.onUpdateList(); + + ViewGroup separatedView = getSeparatedView(); + ViewGroup listView = getListView(); + + for (int i = 0; i < mAdapter.getCount(); i++) { + // generate the view item + View v; + boolean separated = mAdapter.shouldBeSeparated(i); + if (separated) { + v = mAdapter.getView(i, null, separatedView); + } else { + v = mAdapter.getView(i, null, listView); + } + if (separated) { + addToSeparatedView(v, false); + } else { + addToListView(v, shouldReverseListItems()); + } + } + } + + @Override + protected ViewGroup getSeparatedView() { + return findViewById(R.id.separated_button); + } + + @Override + protected ViewGroup getListView() { + return findViewById(android.R.id.list); + } + + protected View getWrapper() { + return getChildAt(0); + } + + /** + * Not used in this implementation of the Global Actions Menu, but necessary for some others. + */ + @Override + public void setDivisionView(View v) { + // do nothing + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 7b3ddf7cfaef..1194a1d7b3f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -833,6 +833,11 @@ public class NotificationPanelView extends PanelView implements return false; } initDownStates(event); + // Do not let touches go to shade or QS if the bouncer is visible, + // but still let user swipe down to expand the panel, dismissing the bouncer. + if (mStatusBar.isBouncerShowing()) { + return true; + } if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mIsExpansionFromHeadsUp = true; MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); @@ -1000,6 +1005,13 @@ public class NotificationPanelView extends PanelView implements if (mBlockTouches || (mQs != null && mQs.isCustomizing())) { return false; } + + // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able to + // pull down QS or expand the shade. + if (mStatusBar.isBouncerShowingScrimmed()) { + return false; + } + initDownStates(event); // Make sure the next touch won't the blocked after the current ends. if (event.getAction() == MotionEvent.ACTION_UP diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 5c169728b14f..2524e34d5762 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -4441,6 +4441,13 @@ public class StatusBar extends SystemUI implements DemoMode, } /** + * @return Whether the security bouncer from Keyguard is showing. + */ + public boolean isBouncerShowingScrimmed() { + return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming(); + } + + /** * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then * return PackageManager for mContext */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 7fe89069b438..ce432534c1bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -484,7 +484,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // by a FLAG_DISMISS_KEYGUARD_ACTIVITY. reset(isOccluding /* hideBouncerWhenShowing*/); } - if (animate && !occluded && mShowing) { + if (animate && !occluded && mShowing && !mBouncer.isShowing()) { mStatusBar.animateKeyguardUnoccluding(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java new file mode 100644 index 000000000000..16d665cbafcd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.leak.RotationUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link ListGridLayout}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class GlobalActionsColumnLayoutTest extends SysuiTestCase { + + private GlobalActionsColumnLayout mColumnLayout; + + @Before + public void setUp() throws Exception { + mColumnLayout = spy((GlobalActionsColumnLayout) + LayoutInflater.from(mContext).inflate(R.layout.global_actions_column, null)); + } + + @Test + public void testShouldReverseListItems() { + doReturn(View.LAYOUT_DIRECTION_LTR).when(mColumnLayout).getCurrentLayoutDirection(); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(true, mColumnLayout.shouldReverseListItems()); + + doReturn(View.LAYOUT_DIRECTION_RTL).when(mColumnLayout).getCurrentLayoutDirection(); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(true, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + } + + @Test + public void testGetAnimationOffsetX() { + doReturn(50f).when(mColumnLayout).getAnimationDistance(); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(50f, mColumnLayout.getAnimationOffsetX(), .01); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(0, mColumnLayout.getAnimationOffsetX(), .01); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(0, mColumnLayout.getAnimationOffsetX(), .01); + } + + @Test + public void testGetAnimationOffsetY() { + doReturn(50f).when(mColumnLayout).getAnimationDistance(); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(0, mColumnLayout.getAnimationOffsetY(), .01); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(-50f, mColumnLayout.getAnimationOffsetY(), .01); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(50f, mColumnLayout.getAnimationOffsetY(), .01); + } + + @Test + public void testSnapToPowerButton_portrait() { + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + doReturn(50).when(mColumnLayout).getPowerButtonOffsetDistance(); + + mColumnLayout.snapToPowerButton(); + assertEquals(Gravity.TOP | Gravity.RIGHT, mColumnLayout.getGravity()); + assertEquals(50, mColumnLayout.getPaddingTop(), .01); + } + + @Test + public void testCenterAlongEdge_portrait() { + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + + mColumnLayout.centerAlongEdge(); + assertEquals(Gravity.CENTER_VERTICAL | Gravity.RIGHT, mColumnLayout.getGravity()); + assertEquals(0, mColumnLayout.getPaddingTop(), .01); + } + + @Test + public void testUpdateSnap_initialState() { + doReturn(false).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should do nothing, since snap has not changed from init state + + verify(mColumnLayout, times(0)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + } + + @Test + public void testUpdateSnap_snapThenSnap() { + doReturn(true).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should snap to power button + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + + mColumnLayout.updateSnap(); // should do nothing, since this is the same state as last time + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + } + + @Test + public void testUpdateSnap_snapThenCenter() { + doReturn(true).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should snap to power button + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + + doReturn(false).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should center to edge + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(1)).centerAlongEdge(); + } + + @Test + public void testShouldSnapToPowerButton_vertical() { + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + doReturn(300).when(mColumnLayout).getPowerButtonOffsetDistance(); + doReturn(1000).when(mColumnLayout).getMeasuredHeight(); + View wrapper = spy(new View(mContext, null)); + doReturn(wrapper).when(mColumnLayout).getWrapper(); + doReturn(500).when(wrapper).getMeasuredHeight(); + + assertEquals(true, mColumnLayout.shouldSnapToPowerButton()); + + doReturn(600).when(mColumnLayout).getMeasuredHeight(); + + assertEquals(false, mColumnLayout.shouldSnapToPowerButton()); + } + + @Test + public void testShouldSnapToPowerButton_horizontal() { + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + doReturn(300).when(mColumnLayout).getPowerButtonOffsetDistance(); + doReturn(1000).when(mColumnLayout).getMeasuredWidth(); + View wrapper = spy(new View(mContext, null)); + doReturn(wrapper).when(mColumnLayout).getWrapper(); + doReturn(500).when(wrapper).getMeasuredWidth(); + + assertEquals(true, mColumnLayout.shouldSnapToPowerButton()); + + doReturn(600).when(mColumnLayout).getMeasuredWidth(); + + assertEquals(false, mColumnLayout.shouldSnapToPowerButton()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java index 3c52e9d86c15..a396f3e8bf46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java @@ -18,12 +18,8 @@ package com.android.systemui.globalactions; import static junit.framework.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; @@ -32,7 +28,6 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; -import com.android.systemui.MultiListLayout; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.leak.RotationUtils; @@ -49,61 +44,12 @@ import org.junit.runner.RunWith; public class GlobalActionsGridLayoutTest extends SysuiTestCase { private GlobalActionsGridLayout mGridLayout; - private TestAdapter mAdapter; private ListGridLayout mListGrid; - private class TestAdapter extends MultiListLayout.MultiListAdapter { - @Override - public void onClickItem(int index) { } - - @Override - public boolean onLongClickItem(int index) { - return true; - } - - @Override - public int countSeparatedItems() { - return -1; - } - - @Override - public int countListItems() { - return -1; - } - - @Override - public boolean shouldBeSeparated(int position) { - return false; - } - - @Override - public int getCount() { - return countSeparatedItems() + countListItems(); - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return -1; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return null; - } - } - - @Before public void setUp() throws Exception { mGridLayout = spy((GlobalActionsGridLayout) LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null)); - mAdapter = spy(new TestAdapter()); - mGridLayout.setAdapter(mAdapter); mListGrid = spy(mGridLayout.getListView()); doReturn(mListGrid).when(mGridLayout).getListView(); } @@ -122,7 +68,7 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { @Test public void testShouldReverseListItems() { - doReturn(View.LAYOUT_DIRECTION_LTR).when(mGridLayout).getLayoutDirection(); + doReturn(View.LAYOUT_DIRECTION_LTR).when(mGridLayout).getCurrentLayoutDirection(); doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); assertEquals(false, mGridLayout.shouldReverseListItems()); @@ -133,7 +79,7 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); assertEquals(true, mGridLayout.shouldReverseListItems()); - doReturn(View.LAYOUT_DIRECTION_RTL).when(mGridLayout).getLayoutDirection(); + doReturn(View.LAYOUT_DIRECTION_RTL).when(mGridLayout).getCurrentLayoutDirection(); doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); assertEquals(true, mGridLayout.shouldReverseListItems()); @@ -185,123 +131,26 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { assertEquals(0f, mGridLayout.getAnimationOffsetY(), .01); } - @Test(expected = IllegalStateException.class) - public void testOnUpdateList_noAdapter() { - mGridLayout.setAdapter(null); - mGridLayout.updateList(); - } - @Test - public void testOnUpdateList_noItems() { - doReturn(0).when(mAdapter).countSeparatedItems(); - doReturn(0).when(mAdapter).countListItems(); - mGridLayout.updateList(); - - ViewGroup separatedView = mGridLayout.getSeparatedView(); - ListGridLayout listView = mGridLayout.getListView(); - - assertEquals(0, separatedView.getChildCount()); - assertEquals(View.GONE, separatedView.getVisibility()); - - verify(mListGrid, times(0)).addItem(any()); - } - - @Test - public void testOnUpdateList_resizesFirstSeparatedItem() { - doReturn(1).when(mAdapter).countSeparatedItems(); - doReturn(0).when(mAdapter).countListItems(); + public void testUpdateSeparatedItemSize() { View firstView = new View(mContext, null); View secondView = new View(mContext, null); - doReturn(firstView).when(mAdapter).getView(eq(0), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(0); + ViewGroup separatedView = mGridLayout.getSeparatedView(); + separatedView.addView(firstView); - mGridLayout.updateList(); + mGridLayout.updateSeparatedItemSize(); ViewGroup.LayoutParams childParams = firstView.getLayoutParams(); assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, childParams.width); assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, childParams.height); - doReturn(2).when(mAdapter).countSeparatedItems(); - doReturn(secondView).when(mAdapter).getView(eq(1), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(1); + separatedView.addView(secondView); - mGridLayout.updateList(); + mGridLayout.updateSeparatedItemSize(); childParams = firstView.getLayoutParams(); assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, childParams.width); assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, childParams.height); - - - } - - @Test - public void testOnUpdateList_onlySeparatedItems() { - doReturn(1).when(mAdapter).countSeparatedItems(); - doReturn(0).when(mAdapter).countListItems(); - View testView = new View(mContext, null); - doReturn(testView).when(mAdapter).getView(eq(0), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(0); - - mGridLayout.updateList(); - - verify(mListGrid, times(0)).addItem(any()); - } - - @Test - public void testOnUpdateList_oneSeparatedOneList() { - doReturn(1).when(mAdapter).countSeparatedItems(); - doReturn(1).when(mAdapter).countListItems(); - View view1 = new View(mContext, null); - View view2 = new View(mContext, null); - - doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(0); - - doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(1); - - mGridLayout.updateList(); - - ViewGroup separatedView = mGridLayout.getSeparatedView(); - - assertEquals(1, separatedView.getChildCount()); - assertEquals(View.VISIBLE, separatedView.getVisibility()); - assertEquals(view1, separatedView.getChildAt(0)); - - verify(mListGrid, times(1)).addItem(view2); - } - - @Test - public void testOnUpdateList_fourInList() { - doReturn(0).when(mAdapter).countSeparatedItems(); - doReturn(4).when(mAdapter).countListItems(); - View view1 = new View(mContext, null); - View view2 = new View(mContext, null); - View view3 = new View(mContext, null); - View view4 = new View(mContext, null); - - doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(0); - - doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(1); - - doReturn(view3).when(mAdapter).getView(eq(2), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(2); - - doReturn(view4).when(mAdapter).getView(eq(3), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(3); - - mGridLayout.updateList(); - - ViewGroup separatedView = mGridLayout.getSeparatedView(); - assertEquals(0, separatedView.getChildCount()); - assertEquals(View.GONE, separatedView.getVisibility()); - - verify(mListGrid, times(1)).addItem(view1); - verify(mListGrid, times(1)).addItem(view2); - verify(mListGrid, times(1)).addItem(view3); - verify(mListGrid, times(1)).addItem(view4); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java new file mode 100644 index 000000000000..16dcd659b3f9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.MultiListLayout; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +/** + * Tests for {@link ListGridLayout}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class GlobalActionsLayoutTest extends SysuiTestCase { + + private TestLayout mLayout; + private TestAdapter mAdapter; + + private class TestAdapter extends MultiListLayout.MultiListAdapter { + @Override + public void onClickItem(int index) { } + + @Override + public boolean onLongClickItem(int index) { + return true; + } + + @Override + public int countSeparatedItems() { + return -1; + } + + @Override + public int countListItems() { + return -1; + } + + @Override + public boolean shouldBeSeparated(int position) { + return false; + } + + @Override + public int getCount() { + return countSeparatedItems() + countListItems(); + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return -1; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return null; + } + } + + private class TestLayout extends GlobalActionsLayout { + ArrayList<View> mSeparatedViews = new ArrayList<>(); + ArrayList<View> mListViews = new ArrayList<>(); + boolean mSeparatedViewVisible = false; + + TestLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean shouldReverseListItems() { + return false; + } + + @Override + public float getAnimationOffsetX() { + return 0; + } + + @Override + public float getAnimationOffsetY() { + return 0; + } + + @Override + protected void addToListView(View v, boolean reverse) { + if (reverse) { + mListViews.add(0, v); + } else { + mListViews.add(v); + } + } + + @Override + protected void addToSeparatedView(View v, boolean reverse) { + if (reverse) { + mSeparatedViews.add(0, v); + } else { + mSeparatedViews.add(v); + } + } + + @Override + protected void setSeparatedViewVisibility(boolean visible) { + mSeparatedViewVisible = visible; + } + } + + @Before + public void setUp() throws Exception { + mLayout = spy(new TestLayout(mContext, null)); + mAdapter = spy(new TestAdapter()); + mLayout.setAdapter(mAdapter); + } + + @Test(expected = IllegalStateException.class) + public void testOnUpdateList_noAdapter() { + mLayout.setAdapter(null); + mLayout.updateList(); + } + + @Test + public void testOnUpdateList_noItems() { + doReturn(0).when(mAdapter).countSeparatedItems(); + doReturn(0).when(mAdapter).countListItems(); + mLayout.updateList(); + + assertEquals(0, mLayout.mSeparatedViews.size()); + assertEquals(0, mLayout.mListViews.size()); + + assertEquals(false, mLayout.mSeparatedViewVisible); + } + + @Test + public void testOnUpdateList_oneSeparatedOneList() { + doReturn(1).when(mAdapter).countSeparatedItems(); + doReturn(1).when(mAdapter).countListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(1); + + mLayout.updateList(); + + assertEquals(1, mLayout.mSeparatedViews.size()); + assertEquals(1, mLayout.mListViews.size()); + assertEquals(view1, mLayout.mSeparatedViews.get(0)); + assertEquals(view2, mLayout.mListViews.get(0)); + } + + + @Test + public void testOnUpdateList_twoSeparatedItems() { + doReturn(2).when(mAdapter).countSeparatedItems(); + doReturn(0).when(mAdapter).countListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(0); + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(1); + + mLayout.updateList(); + + assertEquals(2, mLayout.mSeparatedViews.size()); + assertEquals(0, mLayout.mListViews.size()); + + assertEquals(view1, mLayout.mSeparatedViews.get(0)); + assertEquals(view2, mLayout.mSeparatedViews.get(1)); + + // if separated view has items in it, should be made visible + assertEquals(true, mLayout.mSeparatedViewVisible); + } + + @Test + public void testOnUpdateList_twoSeparatedItems_reverse() { + doReturn(2).when(mAdapter).countSeparatedItems(); + doReturn(0).when(mAdapter).countListItems(); + doReturn(true).when(mLayout).shouldReverseListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(1); + + mLayout.updateList(); + + assertEquals(2, mLayout.mSeparatedViews.size()); + assertEquals(0, mLayout.mListViews.size()); + + // separated view items are not reversed in current implementation, and this is intentional! + assertEquals(view1, mLayout.mSeparatedViews.get(0)); + assertEquals(view2, mLayout.mSeparatedViews.get(1)); + } + + @Test + public void testOnUpdateList_fourInList() { + doReturn(0).when(mAdapter).countSeparatedItems(); + doReturn(4).when(mAdapter).countListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + View view3 = new View(mContext, null); + View view4 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(1); + + doReturn(view3).when(mAdapter).getView(eq(2), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(2); + + doReturn(view4).when(mAdapter).getView(eq(3), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(3); + + mLayout.updateList(); + + assertEquals(0, mLayout.mSeparatedViews.size()); + assertEquals(4, mLayout.mListViews.size()); + assertEquals(view1, mLayout.mListViews.get(0)); + assertEquals(view2, mLayout.mListViews.get(1)); + assertEquals(view3, mLayout.mListViews.get(2)); + assertEquals(view4, mLayout.mListViews.get(3)); + } + + @Test + public void testOnUpdateList_fourInList_reverse() { + doReturn(0).when(mAdapter).countSeparatedItems(); + doReturn(4).when(mAdapter).countListItems(); + doReturn(true).when(mLayout).shouldReverseListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + View view3 = new View(mContext, null); + View view4 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(1); + + doReturn(view3).when(mAdapter).getView(eq(2), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(2); + + doReturn(view4).when(mAdapter).getView(eq(3), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(3); + + mLayout.updateList(); + + assertEquals(0, mLayout.mSeparatedViews.size()); + assertEquals(4, mLayout.mListViews.size()); + assertEquals(view1, mLayout.mListViews.get(3)); + assertEquals(view2, mLayout.mListViews.get(2)); + assertEquals(view3, mLayout.mListViews.get(1)); + assertEquals(view4, mLayout.mListViews.get(0)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 1c6e3b09df44..f50cf5a70cdc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -196,6 +197,17 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mBouncer, never()).setExpansion(anyFloat()); } + @Test + public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() { + mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */); + verify(mStatusBar).animateKeyguardUnoccluding(); + + when(mBouncer.isShowing()).thenReturn(true); + clearInvocations(mStatusBar); + mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */); + verify(mStatusBar, never()).animateKeyguardUnoccluding(); + } + private class TestableStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager { public TestableStatusBarKeyguardViewManager(Context context, diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/values/config.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/values/config.xml new file mode 100644 index 000000000000..ae5cc2bf9883 --- /dev/null +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/values/config.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="config_batterymeterPerimeterPath" translatable="false"> + M 8,1 C 8,0.45 7.55,0 7,0 H 5 C 4.45,0 4,0.45 4,1 H 3 C 1.34,1 0,2.34 0,4 V 17 C 0,18.66 1.34,20 3,20 H 9 C 10.66,20 12,18.66 12,17 V 4 C 12,2.34 10.66,1 9,1 Z M 10.5,4 V 17 C 10.5,17.83 9.83,18.5 9,18.5 H 3 C 2.17,18.5 1.5,17.83 1.5,17 V 4 C 1.5,3.17 2.17,2.5 3,2.5 H 9 C 9.83,2.5 10.5,3.17 10.5,4 Z + </string> + <string name="config_batterymeterFillMask" translatable="false"> + M 10.5,4 V 17 C 10.5,17.83 9.83,18.5 9,18.5 H 3 C 2.17,18.5 1.5,17.83 1.5,17 V 4 C 1.5,3.17 2.17,2.5 3,2.5 H 9 C 9.83,2.5 10.5,3.17 10.5,4 Z + </string> + <string name="config_batterymeterBoltPath" translatable="false"> + M 8.08,9.5 H 7 V 5.99 C 7,5.73 6.65,5.64 6.53,5.87 L 3.7,11.13 C 3.61,11.3 3.73,11.5 3.92,11.5 H 5 V 15.01 C 5,15.27 5.35,15.36 5.47,15.13 L 8.3,9.87 C 8.39,9.7 8.27,9.5 8.08,9.5 Z + </string> + <string name="config_batterymeterPowersavePath" translatable="false"> + M 3.75,11.25 H 5.25 V 12.75 C 5.25,13.16 5.59,13.5 6,13.5 6.41,13.5 6.75,13.16 6.75,12.75 V 11.25 H 8.25 C 8.66,11.25 9,10.91 9,10.5 9,10.09 8.6601,9.75 8.25,9.75 H 6.75 V 8.25 C 6.75,7.84 6.41,7.5 6,7.5 5.59,7.5 5.25,7.84 5.25,8.25 V 9.75 H 3.75 C 3.34,9.75 3,10.09 3,10.5 3,10.91 3.34,11.25 3.75,11.25 Z + </string> +</resources> diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/values/config.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/values/config.xml new file mode 100644 index 000000000000..6b59b6265c54 --- /dev/null +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/values/config.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="config_batterymeterPerimeterPath" translatable="false"> + M 4,0 V 2 H 2.33 C 1.6,2 1,2.6 1,3.33 V 18.66 C 1,19.4 1.6,20 2.33,20 H 9.66 C 10.4,20 11,19.4 11,18.67 V 3.33 C 11,2.6 10.4,2 9.67,2 H 8 V 0 Z + </string> + <string name="config_batterymeterErrorPerimeterPath" translatable="false"> + M 3.5,0 V 0.5 1.5 H 2.3301 C 1.3261,1.5 0.5,2.3261 0.5,3.3301 V 18.16 C 0.5,19.17 1.3261,20 2.3301,20 H 9.6602 C 10.67,20 11.5,19.174 11.5,18.17 V 3.3301 C 11.5,2.3261 10.674,1.5 9.6699,1.5 H 8.5 V 0 Z M 9.1698,2.9999 C 9.6259,2.9999 9.9999,3.374 9.9999,3.83 V 17.67 C 9.9999,18.126 9.6299,18.5 9.1601,18.5 H 2.83 C 2.3741,18.5 2,18.13 2,17.66 V 3.83 C 2,3.374 2.3741,2.9999 2.83,2.9999 Z + </string> + <string name="config_batterymeterFillMask" translatable="false"> + M 4,0 V 2 H 2.33 C 1.6,2 1,2.6 1,3.33 V 18.66 C 1,19.4 1.6,20 2.33,20 H 9.66 C 10.4,20 11,19.4 11,18.67 V 3.33 C 11,2.6 10.4,2 9.67,2 H 8 V 0 Z + </string> + <string name="config_batterymeterBoltPath" translatable="false"> + M 8.58,10 C 8.77,10 8.89,10.2 8.8,10.37 L 5.94,15.74 C 5.7,16.19 5,16.02 5,15.5 V 12 H 3.42 C 3.23,12 3.11,11.8 3.2,11.63 L 6.06,6.26 C 6.3,5.81 7,5.98 7,6.5 V 10 Z + </string> + <string name="config_batterymeterPowersavePath" translatable="false"> + M 9,11 C 9,11.55 8.55,12 8,12 H 7 V 13 C 7,13.55 6.55,14 6,14 5.45,14 5,13.55 5,13 V 12 H 4 C 3.45,12 3,11.55 3,11 3,10.45 3.45,10.005 4,10 H 5 V 9 C 5,8.45 5.45,8 6,8 6.55,8 7,8.45 7,9 V 10 H 8 C 8.55,10 9,10.45 9,11 Z + </string> + <bool name="config_batterymeterDualTone">true</bool> +</resources> diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/values/config.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/values/config.xml new file mode 100644 index 000000000000..ebcac82c695f --- /dev/null +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/values/config.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="config_batterymeterPerimeterPath" translatable="false"> + M 11,1.505 H 8 V 0.995 C 8,0.445 7.55,-0.005 7,-0.005 H 5 C 4.45,-0.005 4,0.445 4,0.995 V 1.505 H 1 C 0.45,1.505 0,1.955 0,2.505 V 19.005 C 0,19.555 0.45,20.005 1,20.005 H 11 C 11.55,20.005 12,19.555 12,19.005 V 2.505 C 12,1.955 11.543,1.505 11,1.505 Z M 10.5,18.505 H 1.5 V 3.005 H 10.5 Z + </string> + + <string name="config_batterymeterFillMask" translatable="false"> + M 10.5,18.505 H 1.5 V 3.005 H 10.5 Z + </string> + <string name="config_batterymeterBoltPath" translatable="false"> + M 3.92,11.5 H 5 V 15.01 C 5,15.17 5.13,15.26 5.25,15.26 5.33,15.26 5.42,15.22 5.47,15.13 L 8.3,9.87 C 8.39,9.7 8.27,9.5 8.08,9.5 H 7 V 5.99 C 7,5.83 6.87,5.74 6.75,5.74 6.67,5.74 6.58,5.78 6.53,5.87 L 3.7,11.13 C 3.61,11.3 3.73,11.5 3.92,11.5 Z + </string> + <string name="config_batterymeterPowersavePath" translatable="false"> + M 3.75,11.25 H 5.25 V 12.75 C 5.25,13.16 5.59,13.5 6,13.5 6.41,13.5 6.75,13.16 6.75,12.75 V 11.25 H 8.25 C 8.66,11.25 9,10.91 9,10.5 9,10.09 8.66,9.7499 8.25,9.7499 H 6.75 V 8.2499 C 6.75,7.8399 6.41,7.4999 6,7.4999 5.59,7.4999 5.2794,7.841 5.25,8.2499 V 9.7499 H 3.75 C 3.34,9.7499 3,10.09 3,10.5 3,10.91 3.3401,11.25 3.75,11.25 Z + </string> +</resources> diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index a7921b5f3892..4399e4267fda 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -161,8 +161,14 @@ final class ContentCapturePerUserService @Override // from PerUserSystemService @GuardedBy("mLock") protected boolean updateLocked(boolean disabled) { - destroyLocked(); final boolean disabledStateChanged = super.updateLocked(disabled); + if (disabledStateChanged) { + // update session content capture enabled state. + for (int i = 0; i < mSessions.size(); i++) { + mSessions.valueAt(i).setContentCaptureEnabledLocked(!disabled); + } + } + destroyLocked(); updateRemoteServiceLocked(disabled); return disabledStateChanged; } @@ -542,7 +548,8 @@ final class ContentCapturePerUserService Slog.v(TAG, "setContentCaptureWhitelist(" + (packages == null ? "null_packages" : packages.size() + " packages") + ", " + (activities == null - ? "null_activities" : activities.size() + " activities") + ")"); + ? "null_activities" : activities.size() + " activities") + ")" + + " for user " + mUserId); } mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index d38dfd409439..2643db1d5851 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -16,6 +16,8 @@ package com.android.server.contentcapture; import static android.service.contentcapture.ContentCaptureService.setClientState; +import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; +import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE; import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; import static android.view.contentcapture.ContentCaptureSession.STATE_ACTIVE; import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; @@ -24,13 +26,16 @@ import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_UP import android.annotation.NonNull; import android.content.ComponentName; +import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.SnapshotData; import android.util.LocalLog; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureSessionId; +import android.view.contentcapture.MainContentCaptureSession; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; @@ -108,6 +113,20 @@ final class ContentCaptureServerSession { } /** + * Changes the {@link ContentCaptureService} enabled state. + */ + @GuardedBy("mLock") + public void setContentCaptureEnabledLocked(boolean enabled) { + try { + final Bundle extras = new Bundle(); + extras.putBoolean(MainContentCaptureSession.EXTRA_ENABLED_STATE, true); + mSessionStateReceiver.send(enabled ? RESULT_CODE_TRUE : RESULT_CODE_FALSE, extras); + } catch (RemoteException e) { + Slog.w(TAG, "Error async reporting result to client: " + e); + } + } + + /** * Notifies the {@link ContentCaptureService} of a snapshot of an activity. */ @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 043daefe0fdb..7abfcea306cc 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -38,6 +38,7 @@ import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; @@ -60,8 +61,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import android.app.ActivityManager; import android.app.usage.UsageEvents; import android.content.Context; -import android.os.Binder; import android.os.Debug; +import android.os.Handler; import android.os.IBinder; import android.os.PowerManagerInternal; import android.os.Process; @@ -78,6 +79,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ProcessStats; import com.android.server.LocalServices; +import com.android.server.ServiceThread; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowProcessController; @@ -148,6 +150,12 @@ public final class OomAdjuster { /** Track all uids that have actively running processes. */ ActiveUids mActiveUids; + /** + * The handler to execute {@link #setProcessGroup} (it may be heavy if the process has many + * threads) for reducing the time spent in {@link #applyOomAdjLocked}. + */ + private final Handler mProcessGroupHandler; + private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet(); private final ActivityManagerService mService; @@ -161,6 +169,28 @@ public final class OomAdjuster { mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class); mConstants = mService.mConstants; mAppCompact = new AppCompactor(mService); + + // The process group is usually critical to the response time of foreground app, so the + // setter should apply it as soon as possible. + final ServiceThread adjusterThread = new ServiceThread(TAG, TOP_APP_PRIORITY_BOOST, + false /* allowIo */); + adjusterThread.start(); + Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP); + mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup"); + final int pid = msg.arg1; + final int group = msg.arg2; + try { + setProcessGroup(pid, group); + } catch (Exception e) { + if (DEBUG_ALL) { + Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + return true; + }); } void initSettings() { @@ -1726,9 +1756,9 @@ public final class OomAdjuster { processGroup = THREAD_GROUP_DEFAULT; break; } - long oldId = Binder.clearCallingIdentity(); + mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( + 0 /* unused */, app.pid, processGroup)); try { - setProcessGroup(app.pid, processGroup); if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { // do nothing if we already switched to RT if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { @@ -1791,13 +1821,9 @@ public final class OomAdjuster { } } } catch (Exception e) { - if (false) { - Slog.w(TAG, "Failed setting process group of " + app.pid - + " to " + app.getCurrentSchedulingGroup()); - Slog.w(TAG, "at location", e); + if (DEBUG_ALL) { + Slog.w(TAG, "Failed setting thread priority of " + app.pid, e); } - } finally { - Binder.restoreCallingIdentity(oldId); } } } diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 74b72210859c..1e0f2051933c 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -63,11 +63,11 @@ public abstract class AuthenticationClient extends ClientMonitor { */ public abstract boolean shouldFrameworkHandleLockout(); - public AuthenticationClient(Context context, Metrics metrics, + public AuthenticationClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, int cookie, boolean requireConfirmation) { - super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId, + super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId, restricted, owner, cookie); mOpId = opId; mRequireConfirmation = requireConfirmation; @@ -126,7 +126,7 @@ public abstract class AuthenticationClient extends ClientMonitor { final BiometricServiceBase.ServiceListener listener = getListener(); - mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated); + mMetricsLogger.action(mConstants.actionBiometricAuth(), authenticated); boolean result = false; try { @@ -225,7 +225,7 @@ public abstract class AuthenticationClient extends ClientMonitor { final int result = getDaemonWrapper().authenticate(mOpId, getGroupId()); if (result != 0) { Slog.w(getLogTag(), "startAuthentication failed, result=" + result); - mMetricsLogger.histogram(mMetrics.tagAuthStartError(), result); + mMetricsLogger.histogram(mConstants.tagAuthStartError(), result); onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); return result; diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 3f856d3e8eb2..d3c62bed7b5f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -138,7 +138,7 @@ public abstract class BiometricServiceBase extends SystemService /** * @return the metrics constants for a biometric implementation. */ - protected abstract Metrics getMetrics(); + protected abstract Constants getConstants(); /** * @param userId @@ -220,7 +220,7 @@ public abstract class BiometricServiceBase extends SystemService public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, int cookie, boolean requireConfirmation) { - super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId, + super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId, groupId, opId, restricted, owner, cookie, requireConfirmation); } @@ -283,7 +283,7 @@ public abstract class BiometricServiceBase extends SystemService IBinder token, ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, final int[] disabledFeatures) { - super(context, getMetrics(), daemon, halDeviceId, token, listener, + super(context, getConstants(), daemon, halDeviceId, token, listener, userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(), disabledFeatures); } @@ -302,7 +302,7 @@ public abstract class BiometricServiceBase extends SystemService DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int templateId, int groupId, int userId, boolean restricted, String owner) { - super(context, getMetrics(), daemon, halDeviceId, token, listener, templateId, groupId, + super(context, getConstants(), daemon, halDeviceId, token, listener, templateId, groupId, userId, restricted, owner, getBiometricUtils()); } @@ -329,7 +329,7 @@ public abstract class BiometricServiceBase extends SystemService ServiceListener listener, int groupId, int userId, boolean restricted, String owner, List<? extends BiometricAuthenticator.Identifier> enrolledList, BiometricUtils utils) { - super(context, getMetrics(), daemon, halDeviceId, token, listener, groupId, userId, + super(context, getConstants(), daemon, halDeviceId, token, listener, groupId, userId, restricted, owner); mEnrolledList = enrolledList; mUtils = utils; @@ -655,7 +655,7 @@ public abstract class BiometricServiceBase extends SystemService @Override public void serviceDied(long cookie) { Slog.e(getTag(), "HAL died"); - mMetricsLogger.count(getMetrics().tagHalDied(), 1); + mMetricsLogger.count(getConstants().tagHalDied(), 1); mHALDeathCount++; mCurrentUserId = UserHandle.USER_NULL; handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, @@ -845,7 +845,7 @@ public abstract class BiometricServiceBase extends SystemService } mHandler.post(() -> { - mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0); + mMetricsLogger.histogram(getConstants().tagAuthToken(), opId != 0L ? 1 : 0); // Get performance stats object for this user. HashMap<Integer, PerformanceStats> pmap diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java index 006558080ef0..942e0501d88d 100644 --- a/services/core/java/com/android/server/biometrics/ClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java @@ -63,7 +63,7 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D private final int mCookie; protected final MetricsLogger mMetricsLogger; - protected final Metrics mMetrics; + protected final Constants mConstants; protected boolean mAlreadyCancelled; protected boolean mAlreadyDone; @@ -80,12 +80,12 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D * permission * @param owner name of the client that owns this */ - public ClientMonitor(Context context, Metrics metrics, + public ClientMonitor(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, boolean restricted, String owner, int cookie) { mContext = context; - mMetrics = metrics; + mConstants = constants; mDaemon = daemon; mHalDeviceId = halDeviceId; mToken = token; @@ -108,7 +108,7 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D } protected String getLogTag() { - return mMetrics.logTag(); + return mConstants.logTag(); } public int getCookie() { @@ -145,6 +145,31 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D public abstract boolean onEnumerationResult( BiometricAuthenticator.Identifier identifier, int remaining); + public int[] getAcquireIgnorelist() { + return new int[0]; + } + public int[] getAcquireVendorIgnorelist() { + return new int[0]; + } + + private boolean blacklistContains(int acquiredInfo, int vendorCode) { + if (acquiredInfo == mConstants.acquireVendorCode()) { + for (int i = 0; i < getAcquireVendorIgnorelist().length; i++) { + if (getAcquireVendorIgnorelist()[i] == vendorCode) { + if (DEBUG) Slog.v(getLogTag(), "Ignoring vendor message: " + vendorCode); + return true; + } + } + } else { + for (int i = 0; i < getAcquireIgnorelist().length; i++) { + if (getAcquireIgnorelist()[i] == acquiredInfo) { + if (DEBUG) Slog.v(getLogTag(), "Ignoring message: " + acquiredInfo); + return true; + } + } + } + return false; + } public boolean isAlreadyDone() { return mAlreadyDone; @@ -160,7 +185,7 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D super.logOnAcquired(mContext, acquiredInfo, vendorCode, getTargetUserId()); if (DEBUG) Slog.v(getLogTag(), "Acquired: " + acquiredInfo + " " + vendorCode); try { - if (mListener != null) { + if (mListener != null && !blacklistContains(acquiredInfo, vendorCode)) { mListener.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode); } return false; // acquisition continues... diff --git a/services/core/java/com/android/server/biometrics/Metrics.java b/services/core/java/com/android/server/biometrics/Constants.java index 02e44e94e395..874fd428ea8a 100644 --- a/services/core/java/com/android/server/biometrics/Metrics.java +++ b/services/core/java/com/android/server/biometrics/Constants.java @@ -16,7 +16,7 @@ package com.android.server.biometrics; -public interface Metrics { +public interface Constants { /** The log tag */ String logTag(); @@ -31,4 +31,6 @@ public interface Metrics { /** Integers for MetricsLogger.action() */ int actionBiometricAuth(); int actionBiometricEnroll(); + + int acquireVendorCode(); }
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index e656d9809582..854528f0654d 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -40,12 +40,12 @@ public abstract class EnrollClient extends ClientMonitor { public abstract boolean shouldVibrate(); - public EnrollClient(Context context, Metrics metrics, + public EnrollClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils, final int[] disabledFeatures) { - super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, + super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); @@ -78,7 +78,7 @@ public abstract class EnrollClient extends ClientMonitor { if (shouldVibrate()) { vibrateSuccess(); } - mMetricsLogger.action(mMetrics.actionBiometricEnroll()); + mMetricsLogger.action(mConstants.actionBiometricEnroll()); try { final BiometricServiceBase.ServiceListener listener = getListener(); if (listener != null) { @@ -105,7 +105,7 @@ public abstract class EnrollClient extends ClientMonitor { disabledFeatures); if (result != 0) { Slog.w(getLogTag(), "startEnroll failed, result=" + result); - mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result); + mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result); onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); return result; diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java index 44ac0373507a..f889d2b41c16 100644 --- a/services/core/java/com/android/server/biometrics/EnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java @@ -30,11 +30,11 @@ import java.util.ArrayList; * A class to keep track of the enumeration state for a given client. */ public abstract class EnumerateClient extends ClientMonitor { - public EnumerateClient(Context context, Metrics metrics, + public EnumerateClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int groupId, int userId, boolean restricted, String owner) { - super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, + super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); } @@ -55,7 +55,7 @@ public abstract class EnumerateClient extends ClientMonitor { if (result != 0) { Slog.w(getLogTag(), "start enumerate for user " + getTargetUserId() + " failed, result=" + result); - mMetricsLogger.histogram(mMetrics.tagEnumerateStartError(), result); + mMetricsLogger.histogram(mConstants.tagEnumerateStartError(), result); onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); return result; diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java index a18f336f3ef4..bccab7b7f67c 100644 --- a/services/core/java/com/android/server/biometrics/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/RemovalClient.java @@ -33,11 +33,11 @@ public abstract class RemovalClient extends ClientMonitor { private final int mBiometricId; private final BiometricUtils mBiometricUtils; - public RemovalClient(Context context, Metrics metrics, + public RemovalClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int biometricId, int groupId, int userId, boolean restricted, String owner, BiometricUtils utils) { - super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, + super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); mBiometricId = biometricId; mBiometricUtils = utils; @@ -60,7 +60,7 @@ public abstract class RemovalClient extends ClientMonitor { if (result != 0) { Slog.w(getLogTag(), "startRemove with id = " + mBiometricId + " failed, result=" + result); - mMetricsLogger.histogram(mMetrics.tagRemoveStartError(), result); + mMetricsLogger.histogram(mConstants.tagRemoveStartError(), result); onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); return result; diff --git a/services/core/java/com/android/server/biometrics/face/FaceMetrics.java b/services/core/java/com/android/server/biometrics/face/FaceConstants.java index 1c5cd5a84402..143eed5db523 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceMetrics.java +++ b/services/core/java/com/android/server/biometrics/face/FaceConstants.java @@ -16,10 +16,12 @@ package com.android.server.biometrics.face; +import android.hardware.face.FaceManager; + import com.android.internal.logging.nano.MetricsProto; -import com.android.server.biometrics.Metrics; +import com.android.server.biometrics.Constants; -public class FaceMetrics implements Metrics { +public class FaceConstants implements Constants { @Override public String logTag() { return FaceService.TAG; @@ -64,4 +66,9 @@ public class FaceMetrics implements Metrics { public int actionBiometricEnroll() { return MetricsProto.MetricsEvent.ACTION_FACE_ENROLL; } + + @Override + public int acquireVendorCode() { + return FaceManager.FACE_ACQUIRED_VENDOR; + } } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index f5a96c786c88..37c15a010e8c 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -65,7 +65,7 @@ import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.EnumerateClient; -import com.android.server.biometrics.Metrics; +import com.android.server.biometrics.Constants; import com.android.server.biometrics.RemovalClient; import org.json.JSONArray; @@ -131,6 +131,26 @@ public class FaceService extends BiometricServiceBase { } @Override + public int[] getAcquireIgnorelist() { + if (isBiometricPrompt()) { + return mBiometricPromptIgnoreList; + } else { + // Keyguard + return mKeyguardIgnoreList; + } + } + + @Override + public int[] getAcquireVendorIgnorelist() { + if (isBiometricPrompt()) { + return mBiometricPromptIgnoreListVendor; + } else { + // Keyguard + return mKeyguardIgnoreListVendor; + } + } + + @Override public boolean onAcquired(int acquireInfo, int vendorCode) { if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) { @@ -205,6 +225,17 @@ public class FaceService extends BiometricServiceBase { final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures) { + + @Override + public int[] getAcquireIgnorelist() { + return mEnrollIgnoreList; + } + + @Override + public int[] getAcquireVendorIgnorelist() { + return mEnrollIgnoreListVendor; + } + @Override public boolean shouldVibrate() { return false; @@ -293,7 +324,7 @@ public class FaceService extends BiometricServiceBase { } final boolean restricted = isRestricted(); - final RemovalClient client = new RemovalClient(getContext(), getMetrics(), + final RemovalClient client = new RemovalClient(getContext(), getConstants(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), faceId, 0 /* groupId */, userId, restricted, token.toString(), getBiometricUtils()) { @Override @@ -310,7 +341,7 @@ public class FaceService extends BiometricServiceBase { checkPermission(MANAGE_BIOMETRIC); final boolean restricted = isRestricted(); - final EnumerateClient client = new EnumerateClient(getContext(), getMetrics(), + final EnumerateClient client = new EnumerateClient(getContext(), getConstants(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), userId, userId, restricted, getContext().getOpPackageName()) { @Override @@ -638,13 +669,20 @@ public class FaceService extends BiometricServiceBase { } } - private final FaceMetrics mFaceMetrics = new FaceMetrics(); + private final FaceConstants mFaceConstants = new FaceConstants(); @GuardedBy("this") private IBiometricsFace mDaemon; // One of the AuthenticationClient constants private int mCurrentUserLockoutMode; + private int[] mBiometricPromptIgnoreList; + private int[] mBiometricPromptIgnoreListVendor; + private int[] mKeyguardIgnoreList; + private int[] mKeyguardIgnoreListVendor; + private int[] mEnrollIgnoreList; + private int[] mEnrollIgnoreListVendor; + /** * Receives callbacks from the HAL. */ @@ -830,6 +868,19 @@ public class FaceService extends BiometricServiceBase { public FaceService(Context context) { super(context); + + mBiometricPromptIgnoreList = getContext().getResources() + .getIntArray(R.array.config_face_acquire_biometricprompt_ignorelist); + mBiometricPromptIgnoreListVendor = getContext().getResources() + .getIntArray(R.array.config_face_acquire_vendor_biometricprompt_ignorelist); + mKeyguardIgnoreList = getContext().getResources() + .getIntArray(R.array.config_face_acquire_keyguard_ignorelist); + mKeyguardIgnoreListVendor = getContext().getResources() + .getIntArray(R.array.config_face_acquire_vendor_keyguard_ignorelist); + mEnrollIgnoreList = getContext().getResources() + .getIntArray(R.array.config_face_acquire_enroll_ignorelist); + mEnrollIgnoreListVendor = getContext().getResources() + .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist); } @Override @@ -855,8 +906,8 @@ public class FaceService extends BiometricServiceBase { } @Override - protected Metrics getMetrics() { - return mFaceMetrics; + protected Constants getConstants() { + return mFaceConstants; } @Override diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintMetrics.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintConstants.java index a1115c8bcfa7..bdaff71a17b9 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintMetrics.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintConstants.java @@ -16,10 +16,12 @@ package com.android.server.biometrics.fingerprint; +import android.hardware.fingerprint.FingerprintManager; + import com.android.internal.logging.nano.MetricsProto; -import com.android.server.biometrics.Metrics; +import com.android.server.biometrics.Constants; -public class FingerprintMetrics implements Metrics { +public class FingerprintConstants implements Constants { @Override public String logTag() { @@ -65,4 +67,9 @@ public class FingerprintMetrics implements Metrics { public int actionBiometricEnroll() { return MetricsProto.MetricsEvent.ACTION_FINGERPRINT_ENROLL; } + + @Override + public int acquireVendorCode() { + return FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR; + } } diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 6ebeaf9ebe59..d91670d20a54 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -68,7 +68,7 @@ import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; import com.android.server.biometrics.ClientMonitor; import com.android.server.biometrics.EnumerateClient; -import com.android.server.biometrics.Metrics; +import com.android.server.biometrics.Constants; import com.android.server.biometrics.RemovalClient; import org.json.JSONArray; @@ -284,7 +284,7 @@ public class FingerprintService extends BiometricServiceBase { } final boolean restricted = isRestricted(); - final RemovalClient client = new RemovalClient(getContext(), getMetrics(), + final RemovalClient client = new RemovalClient(getContext(), getConstants(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), fingerId, groupId, userId, restricted, token.toString(), getBiometricUtils()) { @Override @@ -301,7 +301,7 @@ public class FingerprintService extends BiometricServiceBase { checkPermission(MANAGE_FINGERPRINT); final boolean restricted = isRestricted(); - final EnumerateClient client = new EnumerateClient(getContext(), getMetrics(), + final EnumerateClient client = new EnumerateClient(getContext(), getConstants(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), userId, userId, restricted, getContext().getOpPackageName()) { @Override @@ -557,7 +557,7 @@ public class FingerprintService extends BiometricServiceBase { } } - private final FingerprintMetrics mFingerprintMetrics = new FingerprintMetrics(); + private final FingerprintConstants mFingerprintConstants = new FingerprintConstants(); private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks = new CopyOnWriteArrayList<>(); @@ -736,8 +736,8 @@ public class FingerprintService extends BiometricServiceBase { } @Override - protected Metrics getMetrics() { - return mFingerprintMetrics; + protected Constants getConstants() { + return mFingerprintConstants; } @Override diff --git a/services/core/java/com/android/server/biometrics/iris/IrisService.java b/services/core/java/com/android/server/biometrics/iris/IrisService.java index cb8a772356cc..2817315ac175 100644 --- a/services/core/java/com/android/server/biometrics/iris/IrisService.java +++ b/services/core/java/com/android/server/biometrics/iris/IrisService.java @@ -23,7 +23,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import com.android.server.biometrics.AuthenticationClient; import com.android.server.biometrics.BiometricServiceBase; import com.android.server.biometrics.BiometricUtils; -import com.android.server.biometrics.Metrics; +import com.android.server.biometrics.Constants; import java.util.List; @@ -75,7 +75,7 @@ public class IrisService extends BiometricServiceBase { } @Override - protected Metrics getMetrics() { + protected Constants getConstants() { return null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 55191dbbbf40..cdcb64143ed6 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -26,6 +26,7 @@ import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED; +import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; @@ -111,6 +112,7 @@ import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.Person; +import android.app.RemoteInput; import android.app.StatusBarManager; import android.app.UriGrantsManager; import android.app.admin.DeviceAdminInfo; @@ -3754,7 +3756,7 @@ public class NotificationManagerService extends SystemService { pkg, userId, true, granted); getContext().sendBroadcastAsUser(new Intent( - NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) + ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) .setPackage(pkg) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT), UserHandle.of(userId), null); @@ -3914,7 +3916,7 @@ public class NotificationManagerService extends SystemService { userId, true, granted); getContext().sendBroadcastAsUser(new Intent( - NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) + ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) .setPackage(listener.getPackageName()) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); @@ -3930,7 +3932,9 @@ public class NotificationManagerService extends SystemService { public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant, int userId, boolean granted) { checkCallerIsSystemOrSystemUiOrShell(); - mAssistants.setUserSet(userId, true); + for (UserInfo ui : mUm.getEnabledProfiles(userId)) { + mAssistants.setUserSet(ui.id, true); + } final long identity = Binder.clearCallingIdentity(); try { setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted); @@ -4144,30 +4148,36 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected void setNotificationAssistantAccessGrantedForUserInternal( - ComponentName assistant, int userId, boolean granted) { - if (assistant == null) { - ComponentName allowedAssistant = CollectionUtils.firstOrNull( - mAssistants.getAllowedComponents(userId)); - if (allowedAssistant != null) { - setNotificationAssistantAccessGrantedForUserInternal( - allowedAssistant, userId, false); - } - return; - } - if (!granted || mAllowedManagedServicePackages.test(assistant.getPackageName(), userId, - mAssistants.getRequiredPermission())) { - mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(), - userId, false, granted); - mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(), - userId, true, granted); + ComponentName assistant, int baseUserId, boolean granted) { + List<UserInfo> users = mUm.getEnabledProfiles(baseUserId); + if (users != null) { + for (UserInfo user : users) { + int userId = user.id; + if (assistant == null) { + ComponentName allowedAssistant = CollectionUtils.firstOrNull( + mAssistants.getAllowedComponents(userId)); + if (allowedAssistant != null) { + setNotificationAssistantAccessGrantedForUserInternal( + allowedAssistant, userId, false); + } + continue; + } + if (!granted || mAllowedManagedServicePackages.test(assistant.getPackageName(), + userId, mAssistants.getRequiredPermission())) { + mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(), + userId, false, granted); + mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(), + userId, true, granted); - getContext().sendBroadcastAsUser(new Intent( - NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) - .setPackage(assistant.getPackageName()) - .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - UserHandle.of(userId), null); + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) + .setPackage(assistant.getPackageName()) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), + UserHandle.of(userId), null); - handleSavePolicyFile(); + handleSavePolicyFile(); + } + } } } @@ -4834,17 +4844,35 @@ public class NotificationManagerService extends SystemService { : null; boolean isForegroundCall = CATEGORY_CALL.equals(notification.category) && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0; - // OR message style (which always has a person) + // OR message style (which always has a person) with any remote input Class<? extends Notification.Style> style = notification.getNotificationStyle(); boolean isMessageStyle = Notification.MessagingStyle.class.equals(style); - boolean notificationAppropriateToBubble = isMessageStyle + boolean notificationAppropriateToBubble = + (isMessageStyle && hasValidRemoteInput(notification)) || (peopleList != null && !peopleList.isEmpty() && isForegroundCall); + // OR something that was previously a bubble & still exists boolean bubbleUpdate = oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0; return canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate); } + private boolean hasValidRemoteInput(Notification n) { + // Also check for inline reply + Notification.Action[] actions = n.actions; + if (actions != null) { + // Get the remote inputs + for (int i = 0; i < actions.length; i++) { + Notification.Action action = actions[i]; + RemoteInput[] inputs = action.getRemoteInputs(); + if (inputs != null && inputs.length > 0) { + return true; + } + } + } + return false; + } + private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c0f09d205efe..6f8439b5dbf9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2923,28 +2923,50 @@ public class PackageManagerService extends IPackageManager.Stub // Remove disable package settings for updated system apps that were // removed via an OTA. If the update is no longer present, remove the // app completely. Otherwise, revoke their system privileges. - for (String deletedAppName : possiblyDeletedUpdatedSystemApps) { - PackageParser.Package deletedPkg = mPackages.get(deletedAppName); - mSettings.removeDisabledSystemPackageLPw(deletedAppName); + for (int i = possiblyDeletedUpdatedSystemApps.size() - 1; i >= 0; --i) { + final String packageName = possiblyDeletedUpdatedSystemApps.get(i); + final PackageParser.Package pkg = mPackages.get(packageName); final String msg; - if (deletedPkg == null) { + + // remove from the disabled system list; do this first so any future + // scans of this package are performed without this state + mSettings.removeDisabledSystemPackageLPw(packageName); + + if (pkg == null) { // should have found an update, but, we didn't; remove everything - msg = "Updated system package " + deletedAppName + msg = "Updated system package " + packageName + " no longer exists; removing its data"; // Actual deletion of code and data will be handled by later // reconciliation step } else { // found an update; revoke system privileges - msg = "Updated system package + " + deletedAppName - + " no longer exists; revoking system privileges"; + msg = "Updated system package " + packageName + + " no longer exists; rescanning package on data"; + + // NOTE: We don't do anything special if a stub is removed from the + // system image. But, if we were [like removing the uncompressed + // version from the /data partition], this is where it'd be done. + + // remove the package from the system and re-scan it without any + // special privileges + removePackageLI(pkg, true); + try { + final File codePath = new File(pkg.applicationInfo.getCodePath()); + scanPackageTracedLI(codePath, 0, scanFlags, 0, null); + } catch (PackageManagerException e) { + Slog.e(TAG, "Failed to parse updated, ex-system package: " + + e.getMessage()); + } + } - // Don't do anything if a stub is removed from the system image. If - // we were to remove the uncompressed version from the /data partition, - // this is where it'd be done. + // one final check. if we still have a package setting [ie. it was + // previously scanned and known to the system], but, we don't have + // a package [ie. there was an error scanning it from the /data + // partition], completely remove the package data. + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null && mPackages.get(packageName) == null) { + removePackageDataLIF(ps, null, null, 0, false); - final PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName); - deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; - deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM; } logCriticalInfo(Log.WARN, msg); } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 6ce42ec4cb44..b6a5be807fb6 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1108,6 +1108,7 @@ final class AccessibilityController { // the window manager is still looking for where to put it. // We will do the work when we get a focus change callback. // TODO(b/112273690): Support multiple displays + // TODO(b/129098348): Support embedded displays if (mService.getDefaultDisplayContentLocked().mCurrentFocus == null) { return; } @@ -1400,7 +1401,28 @@ final class AccessibilityController { if (w.isVisibleLw()) { outWindows.put(mTempLayer++, w); } - }, false /* traverseTopToBottom */ ); + }, false /* traverseTopToBottom */); + mService.mRoot.forAllWindows(w -> { + final WindowState win = findRootDisplayParentWindow(w); + if (win != null && win.getDisplayContent().isDefaultDisplay && w.isVisibleLw()) { + // TODO(b/129098348): insert windows on child displays into outWindows based on + // root-display-parent window. + outWindows.put(mTempLayer++, w); + } + }, false /* traverseTopToBottom */); + } + + private WindowState findRootDisplayParentWindow(WindowState win) { + WindowState displayParentWindow = win.getDisplayContent().getParentWindow(); + if (displayParentWindow == null) { + return null; + } + WindowState candidate = displayParentWindow; + while (candidate != null) { + displayParentWindow = candidate; + candidate = displayParentWindow.getDisplayContent().getParentWindow(); + } + return displayParentWindow; } private class MyHandler extends Handler { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4278860a0d77..802683a67f80 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -916,8 +916,12 @@ final class ActivityRecord extends ConfigurationContainer { } } + static boolean isResolverActivity(String className) { + return ResolverActivity.class.getName().equals(className); + } + boolean isResolverActivity() { - return ResolverActivity.class.getName().equals(mActivityComponent.getClassName()); + return isResolverActivity(mActivityComponent.getClassName()); } boolean isResolverOrChildActivity() { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 7eac07c47741..919141c13622 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -171,7 +171,12 @@ public class ActivityStartController { void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) { final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); - options.setLaunchActivityType(ACTIVITY_TYPE_HOME); + if (!ActivityRecord.isResolverActivity(aInfo.name)) { + // The resolver activity shouldn't be put in home stack because when the foreground is + // standard type activity, the resolver activity should be put on the top of current + // foreground instead of bring home stack to front. + options.setLaunchActivityType(ACTIVITY_TYPE_HOME); + } options.setLaunchDisplayId(displayId); mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason) .setOutActivity(tmpOutRecord) diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 4ef8753bd131..b6840fad2dda 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1038,6 +1038,12 @@ class ActivityStarter { } } } + // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission + if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { + Slog.w(TAG, "Background activity start for " + callingPackage + + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); + return false; + } // anything that has fallen through would currently be aborted Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage + "; callingUid: " + callingUid @@ -2075,8 +2081,8 @@ class ActivityStarter { final ActivityRecord curTop = (focusStack == null) ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop); final TaskRecord topTask = curTop != null ? curTop.getTaskRecord() : null; - differentTopTask = topTask != null - && (topTask != intentActivity.getTaskRecord() || topTask != focusStack.topTask()); + differentTopTask = topTask != intentActivity.getTaskRecord() + || (focusStack != null && topTask != focusStack.topTask()); } else { // The existing task should always be different from those in other displays. differentTopTask = true; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7bc9600f50b4..4a6aa336e36f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -108,12 +108,10 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITC import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_ACTIVITY_ID; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; -import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID; import static com.android.server.wm.ActivityTaskManagerService.H.REPORT_TIME_TRACKER_MSG; import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; @@ -136,6 +134,7 @@ import android.app.ActivityTaskManager; import android.app.ActivityThread; import android.app.AlertDialog; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.Dialog; import android.app.IActivityController; import android.app.IActivityTaskManager; @@ -216,7 +215,6 @@ import android.text.format.Time; import android.util.ArrayMap; import android.util.EventLog; import android.util.Log; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -875,6 +873,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getUserManager().hasUserRestriction(restriction, userId); } + boolean hasSystemAlertWindowPermission(int callingUid, int callingPid, String callingPackage) { + final int mode = getAppOpsService().noteOperation(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + callingUid, callingPackage); + if (mode == AppOpsManager.MODE_DEFAULT) { + return checkPermission(Manifest.permission.SYSTEM_ALERT_WINDOW, callingPid, callingUid) + == PERMISSION_GRANTED; + } + return mode == AppOpsManager.MODE_ALLOWED; + } + protected RecentTasks createRecentTasks() { return new RecentTasks(this, mStackSupervisor); } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index a3cef7f7219d..8785b01ecacf 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -78,6 +78,7 @@ import static com.android.server.wm.WindowManagerService.H.NOTIFY_ACTIVITY_DRAWN import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.logWithStack; +import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; @@ -638,8 +639,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // If we are being set visible, and the starting window is not yet displayed, // then make sure it doesn't get displayed. if (startingWindow != null && !startingWindow.isDrawnLw()) { - startingWindow.mPolicyVisibility = false; - startingWindow.mPolicyVisibilityAfterAnim = false; + startingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); + startingWindow.mLegacyPolicyVisibilityAfterAnim = false; } // We are becoming visible, so better freeze the screen with the windows that are @@ -1932,7 +1933,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree + ", isAnimationSet=" + isSelfAnimating()); if (!w.isDrawnLw()) { Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController - + " pv=" + w.mPolicyVisibility + + " pv=" + w.isVisibleByPolicy() + " mDrawState=" + winAnimator.drawStateToString() + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested + " a=" + isSelfAnimating()); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 41292d2b232b..b4b60ea4bb6f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -144,6 +144,7 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; @@ -541,6 +542,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final InsetsStateController mInsetsStateController; + /** @see #getParentWindow() */ + private WindowState mParentWindow; + + private Point mLocationInParentWindow = new Point(); private SurfaceControl mParentSurfaceControl; private InputWindowHandle mPortalWindowHandle; @@ -4562,7 +4567,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo token2.mOwnerCanManageAppTokens) ? -1 : 1; private final Predicate<WindowState> mGetOrientingWindow = w -> { - if (!w.isVisibleLw() || !w.mPolicyVisibilityAfterAnim) { + if (!w.isVisibleLw() || !w.mLegacyPolicyVisibilityAfterAnim) { return false; } final int req = w.mAttrs.screenOrientation; @@ -4923,11 +4928,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and - * {@link #mOverlayLayer} to the specified surfaceControl. + * {@link #mOverlayLayer} to the specified SurfaceControl. * + * @param win The window which owns the SurfaceControl. This indicates the z-order of the + * windows of this display against the windows on the parent display. * @param sc The new SurfaceControl, where the DisplayContent's surfaces will be re-parented to. */ - void reparentDisplayContent(SurfaceControl sc) { + void reparentDisplayContent(WindowState win, SurfaceControl sc) { + mParentWindow = win; mParentSurfaceControl = sc; if (mPortalWindowHandle == null) { mPortalWindowHandle = createPortalWindowHandle(sc.toString()); @@ -4936,6 +4944,41 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo .reparent(mWindowingLayer, sc).reparent(mOverlayLayer, sc); } + /** + * Get the window which owns the surface that this DisplayContent is re-parented to. + * + * @return the parent window. + */ + WindowState getParentWindow() { + return mParentWindow; + } + + /** + * Update the location of this display in the parent window. This enables windows in this + * display to compute the global transformation matrix. + * + * @param win The parent window of this display. + * @param x The x coordinate in the parent window. + * @param y The y coordinate in the parent window. + */ + void updateLocation(WindowState win, int x, int y) { + if (mParentWindow != win) { + throw new IllegalArgumentException( + "The given window is not the parent window of this display."); + } + if (mLocationInParentWindow.x != x || mLocationInParentWindow.y != y) { + mLocationInParentWindow.x = x; + mLocationInParentWindow.y = y; + if (mWmService.mAccessibilityController != null) { + mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(); + } + } + } + + Point getLocationInParentWindow() { + return mLocationInParentWindow; + } + @VisibleForTesting SurfaceControl getWindowingLayer() { return mWindowingLayer; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f67b11b26b12..402ec5976840 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -29,12 +29,11 @@ import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; +import android.view.InsetsSource; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; -import android.view.InsetsSource; -import android.view.InsetsSourceControl; -import android.view.ViewRootImpl; import com.android.internal.util.function.TriConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -142,7 +141,7 @@ class InsetsSourceProvider { mStateController.notifyControlChanged(mControllingWin); } } - setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.mPolicyVisibility + setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy() && !mWin.mGivenInsetsPending); } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index b33f8c7ad658..34273f3f23a5 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -426,6 +426,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override + public void reparentDisplayContent(IWindow window, SurfaceControl sc, int displayId) { + mService.reparentDisplayContent(window, sc, displayId); + } + + @Override + public void updateDisplayContentLocation(IWindow window, int x, int y, int displayId) { + mService.updateDisplayContentLocation(window, x, y, displayId); + } + + @Override public void updateTapExcludeRegion(IWindow window, int regionId, Region region) { final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4eddb3016205..7c1ea208e270 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1880,7 +1880,8 @@ public class WindowManagerService extends IWindowManager.Stub // We need to report touchable region changes to accessibility. if (mAccessibilityController != null - && w.getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) { + && (w.getDisplayContent().getDisplayId() == DEFAULT_DISPLAY + || w.getDisplayContent().getParentWindow() != null)) { mAccessibilityController.onSomeWindowResizedOrMovedLocked(); } } @@ -2012,7 +2013,8 @@ public class WindowManagerService extends IWindowManager.Stub } if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0) && (mAccessibilityController != null) - && (win.getDisplayId() == DEFAULT_DISPLAY)) { + && (win.getDisplayId() == DEFAULT_DISPLAY + || win.getDisplayContent().getParentWindow() != null)) { // No move or resize, but the controller checks for title changes as well mAccessibilityController.onSomeWindowResizedOrMovedLocked(); } @@ -5300,7 +5302,7 @@ public class WindowManagerService extends IWindowManager.Stub ": removed=" + win.mRemoved + " visible=" + win.isVisibleLw() + " mHasSurface=" + win.mHasSurface + " drawState=" + win.mWinAnimator.mDrawState); - if (win.mRemoved || !win.mHasSurface || !win.mPolicyVisibility) { + if (win.mRemoved || !win.mHasSurface || !win.isVisibleByPolicy()) { // Window has been removed or hidden; no draw will now happen, so stop waiting. if (DEBUG_SCREEN_ON) Slog.w(TAG_WM, "Aborted waiting for drawn: " + win); mWaitingForDrawn.remove(win); @@ -6703,6 +6705,61 @@ public class WindowManagerService extends IWindowManager.Stub } } + private void checkCallerOwnsDisplay(int displayId) { + final Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + throw new IllegalArgumentException( + "Cannot find display for non-existent displayId: " + displayId); + } + + final int callingUid = Binder.getCallingUid(); + final int displayOwnerUid = display.getOwnerUid(); + if (callingUid != displayOwnerUid) { + throw new SecurityException("The caller doesn't own the display."); + } + } + + /** @see Session#reparentDisplayContent(IWindow, SurfaceControl, int) */ + void reparentDisplayContent(IWindow client, SurfaceControl sc, int displayId) { + checkCallerOwnsDisplay(displayId); + + synchronized (mGlobalLock) { + final long token = Binder.clearCallingIdentity(); + try { + final WindowState win = windowForClientLocked(null, client, false); + if (win == null) { + Slog.w(TAG_WM, "Bad requesting window " + client); + return; + } + getDisplayContentOrCreate(displayId, null).reparentDisplayContent(win, sc); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + /** @see Session#updateDisplayContentLocation(IWindow, int, int, int) */ + void updateDisplayContentLocation(IWindow client, int x, int y, int displayId) { + checkCallerOwnsDisplay(displayId); + + synchronized (mGlobalLock) { + final long token = Binder.clearCallingIdentity(); + try { + final WindowState win = windowForClientLocked(null, client, false); + if (win == null) { + Slog.w(TAG_WM, "Bad requesting window " + client); + return; + } + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent != null) { + displayContent.updateLocation(win, x, y); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: @@ -7538,31 +7595,6 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void reparentDisplayContent(int displayId, SurfaceControl sc) { - final Display display = mDisplayManager.getDisplay(displayId); - if (display == null) { - throw new IllegalArgumentException( - "Can't reparent display for non-existent displayId: " + displayId); - } - - final int callingUid = Binder.getCallingUid(); - final int displayOwnerUid = display.getOwnerUid(); - if (callingUid != displayOwnerUid) { - throw new SecurityException("Only owner of the display can reparent surfaces to it."); - } - - synchronized (mGlobalLock) { - long token = Binder.clearCallingIdentity(); - try { - DisplayContent displayContent = getDisplayContentOrCreate(displayId, null); - displayContent.reparentDisplayContent(sc); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - @Override public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) { boolean shouldWaitForAnimToComplete = false; if (ev instanceof KeyEvent) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e39cd56a938c..2a621be8071a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -251,18 +251,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int mSeq; int mViewVisibility; int mSystemUiVisibility; + /** - * The visibility of the window based on policy like {@link WindowManagerPolicy}. + * The visibility flag of the window based on policy like {@link WindowManagerPolicy}. * Normally set by calling {@link #showLw} and {@link #hideLw}. + * + * TODO: b/131253938 This will eventually be split into individual visibility policy flags. + */ + static final int LEGACY_POLICY_VISIBILITY = 1; + /** + * The visibility flag that determines whether this window is visible for the current user. + */ + private static final int VISIBLE_FOR_USER = 1 << 1; + private static final int POLICY_VISIBILITY_ALL = VISIBLE_FOR_USER | LEGACY_POLICY_VISIBILITY; + /** + * The Bitwise-or of flags that contribute to visibility of the WindowState */ - boolean mPolicyVisibility = true; + private int mPolicyVisibility = POLICY_VISIBILITY_ALL; + /** - * What {@link #mPolicyVisibility} should be set to after a transition animation. - * For example, {@link #mPolicyVisibility} might true during an exit animation to hide it and - * then set to the value of {@link #mPolicyVisibilityAfterAnim} which is false after the exit - * animation is done. + * Whether {@link #LEGACY_POLICY_VISIBILITY} flag should be set after a transition animation. + * For example, {@link #LEGACY_POLICY_VISIBILITY} might be set during an exit animation to hide + * it and then unset when the value of {@link #mLegacyPolicyVisibilityAfterAnim} is false + * after the exit animation is done. + * + * TODO: b/131253938 Determine whether this can be changed to use a visibility flag instead. */ - boolean mPolicyVisibilityAfterAnim = true; + boolean mLegacyPolicyVisibilityAfterAnim = true; // overlay window is hidden because the owning app is suspended private boolean mHiddenWhileSuspended; private boolean mAppOpVisibility = true; @@ -1414,13 +1429,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean isVisible() { - return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility + return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy() // If we don't have a provider, this window isn't used as a window generating // insets, so nobody can hide it over the inset APIs. && (mInsetProvider == null || mInsetProvider.isClientVisible()); } /** + * Ensures that all the policy visibility bits are set. + * @return {@code true} if all flags about visiblity are set + */ + boolean isVisibleByPolicy() { + return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL; + } + + void clearPolicyVisibilityFlag(int policyVisibilityFlag) { + mPolicyVisibility &= ~policyVisibilityFlag; + } + + void setPolicyVisibilityFlag(int policyVisibilityFlag) { + mPolicyVisibility |= policyVisibilityFlag; + } + + private boolean isLegacyPolicyVisibility() { + return (mPolicyVisibility & LEGACY_POLICY_VISIBILITY) != 0; + } + + /** * @return {@code true} if the window would be visible if we'd ignore policy visibility, * {@code false} otherwise. */ @@ -1470,7 +1505,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean isVisibleOrAdding() { final AppWindowToken atoken = mAppToken; return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) - && mPolicyVisibility && !isParentWindowHidden() + && isVisibleByPolicy() && !isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested) && !mAnimatingExit && !mDestroying; } @@ -1481,7 +1516,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * being visible. */ boolean isOnScreen() { - if (!mHasSurface || mDestroying || !mPolicyVisibility) { + if (!mHasSurface || mDestroying || !isVisibleByPolicy()) { return false; } final AppWindowToken atoken = mAppToken; @@ -1522,7 +1557,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final boolean parentAndClientVisible = !isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden(); - return mHasSurface && mPolicyVisibility && !mDestroying + return mHasSurface && isVisibleByPolicy() && !mDestroying && (parentAndClientVisible || isAnimating()); } @@ -1551,7 +1586,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public boolean isDisplayedLw() { final AppWindowToken atoken = mAppToken; - return isDrawnLw() && mPolicyVisibility + return isDrawnLw() && isVisibleByPolicy() && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested)) || isAnimating()); } @@ -2057,8 +2092,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController + " relayoutCalled=" + mRelayoutCalled + " viewVis=" + mViewVisibility - + " policyVis=" + mPolicyVisibility - + " policyVisAfterAnim=" + mPolicyVisibilityAfterAnim + + " policyVis=" + isVisibleByPolicy() + + " policyVisAfterAnim=" + mLegacyPolicyVisibilityAfterAnim + " parentHidden=" + isParentWindowHidden() + " exiting=" + mAnimatingExit + " destroying=" + mDestroying); if (mAppToken != null) { @@ -2192,7 +2227,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (isHiddenFromUserLocked()) { if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this + ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid); - hideLw(false); + clearPolicyVisibilityFlag(VISIBLE_FOR_USER); + } else { + setPolicyVisibilityFlag(VISIBLE_FOR_USER); } } @@ -2284,13 +2321,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void checkPolicyVisibilityChange() { - if (mPolicyVisibility != mPolicyVisibilityAfterAnim) { + if (isLegacyPolicyVisibility() != mLegacyPolicyVisibilityAfterAnim) { if (DEBUG_VISIBILITY) { Slog.v(TAG, "Policy visibility changing after anim in " + - mWinAnimator + ": " + mPolicyVisibilityAfterAnim); + mWinAnimator + ": " + mLegacyPolicyVisibilityAfterAnim); + } + if (mLegacyPolicyVisibilityAfterAnim) { + setPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); + } else { + clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); } - mPolicyVisibility = mPolicyVisibilityAfterAnim; - if (!mPolicyVisibility) { + if (!isVisibleByPolicy()) { mWinAnimator.hide("checkPolicyVisibilityChange"); if (isFocused()) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, @@ -2531,7 +2572,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean showLw(boolean doAnimation, boolean requestAnim) { - if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { + if (isLegacyPolicyVisibility() && mLegacyPolicyVisibilityAfterAnim) { // Already showing. return false; } @@ -2558,18 +2599,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this); if (doAnimation) { if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility=" - + mPolicyVisibility + " animating=" + isAnimating()); + + isLegacyPolicyVisibility() + " animating=" + isAnimating()); if (!mToken.okToAnimate()) { doAnimation = false; - } else if (mPolicyVisibility && !isAnimating()) { + } else if (isLegacyPolicyVisibility() && !isAnimating()) { // Check for the case where we are currently visible and // not animating; we do not want to do animation at such a // point to become visible when we already are. doAnimation = false; } } - mPolicyVisibility = true; - mPolicyVisibilityAfterAnim = true; + setPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); + mLegacyPolicyVisibilityAfterAnim = true; if (doAnimation) { mWinAnimator.applyAnimationLocked(TRANSIT_ENTER, true); } @@ -2593,7 +2634,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP doAnimation = false; } } - boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility; + boolean current = + doAnimation ? mLegacyPolicyVisibilityAfterAnim : isLegacyPolicyVisibility(); if (!current) { // Already hiding. return false; @@ -2604,11 +2646,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP doAnimation = false; } } - mPolicyVisibilityAfterAnim = false; + mLegacyPolicyVisibilityAfterAnim = false; final boolean isFocused = isFocused(); if (!doAnimation) { if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this); - mPolicyVisibility = false; + clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); // Window is no longer visible -- make sure if we were waiting // for it to be displayed before enabling the display, that // we allow the display to be enabled now. @@ -3100,7 +3142,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } //TODO (multidisplay): Accessibility supported only for the default display. - if (mWmService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) { + if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY + || getDisplayContent().getParentWindow() != null)) { mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(); } @@ -3443,11 +3486,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "mSeq=" + mSeq + " mSystemUiVisibility=0x" + Integer.toHexString(mSystemUiVisibility)); } - if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility + if (!isVisibleByPolicy() || !mLegacyPolicyVisibilityAfterAnim || !mAppOpVisibility || isParentWindowHidden() || mPermanentlyHidden || mForceHideNonSystemOverlayWindow || mHiddenWhileSuspended) { - pw.println(prefix + "mPolicyVisibility=" + mPolicyVisibility - + " mPolicyVisibilityAfterAnim=" + mPolicyVisibilityAfterAnim + pw.println(prefix + "mPolicyVisibility=" + isVisibleByPolicy() + + " mLegacyPolicyVisibilityAfterAnim=" + mLegacyPolicyVisibilityAfterAnim + " mAppOpVisibility=" + mAppOpVisibility + " parentHidden=" + isParentWindowHidden() + " mPermanentlyHidden=" + mPermanentlyHidden @@ -3904,7 +3947,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + ": mDrawState=" + mWinAnimator.drawStateToString() + " readyForDisplay=" + isReadyForDisplay() + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING) - + " during animation: policyVis=" + mPolicyVisibility + + " during animation: policyVis=" + isVisibleByPolicy() + " parentHidden=" + isParentWindowHidden() + " tok.hiddenRequested=" + (mAppToken != null && mAppToken.hiddenRequested) @@ -4165,7 +4208,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } //TODO (multidisplay): Accessibility is supported only for the default display. - if (mWmService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) { + if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY + || getDisplayContent().getParentWindow() != null)) { mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(); } @@ -4313,7 +4357,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + ", animating=" + isAnimating()); if (!isDrawnLw()) { Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController - + " pv=" + mPolicyVisibility + + " pv=" + isVisibleByPolicy() + " mDrawState=" + mWinAnimator.mDrawState + " ph=" + isParentWindowHidden() + " th=" + (mAppToken != null ? mAppToken.hiddenRequested : false) @@ -4604,6 +4648,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int x = mSurfacePosition.x; int y = mSurfacePosition.y; + // We might be on a display which has been re-parented to a view in another window, so here + // computes the global location of our display. + DisplayContent dc = getDisplayContent(); + while (dc != null && dc.getParentWindow() != null) { + final WindowState displayParent = dc.getParentWindow(); + x += displayParent.mWindowFrames.mFrame.left - displayParent.mAttrs.surfaceInsets.left + + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f); + y += displayParent.mWindowFrames.mFrame.top - displayParent.mAttrs.surfaceInsets.top + + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f); + dc = displayParent.getDisplayContent(); + } + // If changed, also adjust transformFrameToSurfacePosition final WindowContainer parent = getParent(); if (isChildWindow()) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 780d4714275b..20e1ac6c913d 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -256,7 +256,7 @@ class WindowStateAnimator { mWin.checkPolicyVisibilityChange(); final DisplayContent displayContent = mWin.getDisplayContent(); - if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.mPolicyVisibility) { + if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.isVisibleByPolicy()) { // Upon completion of a not-visible to visible status bar animation a relayout is // required. if (displayContent != null) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c5a206882695..22231c0ab09b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -8034,6 +8034,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalArgumentException("Component " + who + " not installed for userId:" + userHandle); } + final boolean hasIncompatibleAccountsOrNonAdb = hasIncompatibleAccountsOrNonAdbNoLock(userHandle, who); synchronized (getLockObject()) { @@ -8539,9 +8540,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } enforceCanManageProfileAndDeviceOwners(); - if ((mIsWatch || hasUserSetupCompleted(userHandle)) && !isCallerWithSystemUid()) { - throw new IllegalStateException("Cannot set the profile owner on a user which is " - + "already set-up"); + + if ((mIsWatch || hasUserSetupCompleted(userHandle))) { + if (!isCallerWithSystemUid()) { + throw new IllegalStateException("Cannot set the profile owner on a user which is " + + "already set-up"); + } + + if (!mIsWatch) { + // Only the default supervision profile owner can be set as profile owner after SUW + final String supervisor = mContext.getResources().getString( + com.android.internal.R.string + .config_defaultSupervisionProfileOwnerComponent); + if (supervisor == null) { + throw new IllegalStateException("Unable to set profile owner post-setup, no" + + "default supervisor profile owner defined"); + } + + final ComponentName supervisorComponent = ComponentName.unflattenFromString( + supervisor); + if (!owner.equals(supervisorComponent)) { + throw new IllegalStateException("Unable to set non-default profile owner" + + " post-setup " + owner); + } + } } } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 6ec864c05258..f4a6231e43fb 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -16,6 +16,7 @@ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN; import static android.app.usage.UsageEvents.Event.SLICE_PINNED; import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV; @@ -821,6 +822,41 @@ public class AppStandbyControllerTests { } @Test + public void testInitialForegroundServiceTimeout() throws Exception { + setChargingState(mController, false); + + mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; + // Make sure app is in NEVER bucket + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, + REASON_MAIN_FORCED, mInjector.mElapsedRealtime); + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_NEVER); + + mInjector.mElapsedRealtime += 100; + + // Trigger a FOREGROUND_SERVICE_START and verify bucket + reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1); + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_ACTIVE); + + // Verify it's still in ACTIVE close to end of timeout + mInjector.mElapsedRealtime += mController.mInitialForegroundServiceStartTimeoutMillis - 100; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_ACTIVE); + + // Verify bucket moves to RARE after timeout + mInjector.mElapsedRealtime += 200; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RARE); + + // Trigger a FOREGROUND_SERVICE_START again + reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1); + mController.checkIdleStates(USER_ID); + // Bucket should not be immediately elevated on subsequent service starts + assertBucket(STANDBY_BUCKET_RARE); + } + + @Test public void testPredictionNotOverridden() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); assertBucket(STANDBY_BUCKET_ACTIVE); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index cbca087872d9..8d56bc400d14 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -62,6 +62,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -85,6 +86,7 @@ import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; +import android.app.RemoteInput; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -96,6 +98,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; @@ -327,6 +330,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); + doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); + mService = new TestableNotificationManagerService(mContext); // Use this testable looper. @@ -375,20 +380,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true); - try { - mService.init(mTestableLooper.getLooper(), - mPackageManager, mPackageManagerClient, mockLightsManager, - mListeners, mAssistants, mConditionProviders, - mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, - mGroupHelper, mAm, mAppUsageStats, - mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, - mAppOpsManager, mUm); - mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + + mService.init(mTestableLooper.getLooper(), + mPackageManager, mPackageManagerClient, mockLightsManager, + mListeners, mAssistants, mConditionProviders, + mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, + mGroupHelper, mAm, mAppUsageStats, + mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, + mAppOpsManager, mUm); + mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + mService.setAudioManager(mAudioManager); // Tests call directly into the Binder. @@ -2080,14 +2081,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSetListenerAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationListenerAccessGrantedForUser( - c, user.getIdentifier(), true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + mBinderService.setNotificationListenerAccessGrantedForUser(c, user.getIdentifier(), true); + verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any()); verify(mListeners, times(1)).setPackageOrComponentEnabled( @@ -2101,15 +2096,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetAssistantAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 10; + uis.add(ui); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationAssistantAccessGrantedForUser( - c, user.getIdentifier(), true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + when(mUm.getEnabledProfiles(10)).thenReturn(uis); + + mBinderService.setNotificationAssistantAccessGrantedForUser(c, user.getIdentifier(), true); verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any()); verify(mAssistants, times(1)).setPackageOrComponentEnabled( @@ -2150,14 +2144,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSetDndAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationPolicyAccessGrantedForUser( - c.getPackageName(), user.getIdentifier(), true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + mBinderService.setNotificationPolicyAccessGrantedForUser( + c.getPackageName(), user.getIdentifier(), true); verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any()); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( @@ -2171,13 +2159,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetListenerAccess() throws Exception { ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationListenerAccessGranted(c, true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + mBinderService.setNotificationListenerAccessGranted(c, true); verify(mListeners, times(1)).setPackageOrComponentEnabled( c.flattenToString(), 0, true, true); @@ -2189,19 +2171,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetAssistantAccess() throws Exception { + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 0; + uis.add(ui); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationAssistantAccessGranted(c, true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + + mBinderService.setNotificationAssistantAccessGranted(c, true); + + verify(mAssistants, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), 0, true, true); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), 0, false, true); + verify(mListeners, never()).setPackageOrComponentEnabled( + any(), anyInt(), anyBoolean(), anyBoolean()); + } + + @Test + public void testSetAssistantAccess_multiProfile() throws Exception { + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 0; + uis.add(ui); + UserInfo ui10 = new UserInfo(); + ui10.id = 10; + uis.add(ui10); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); + ComponentName c = ComponentName.unflattenFromString("package/Component"); + + mBinderService.setNotificationAssistantAccessGranted(c, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( c.flattenToString(), 0, true, true); + verify(mAssistants, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), 10, true, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( c.flattenToString(), 0, false, true); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), 10, false, true); verify(mListeners, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean()); } @@ -2212,14 +2220,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ComponentName c = ComponentName.unflattenFromString("package/Component"); componentList.add(c); when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 0; + uis.add(ui); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); - try { - mBinderService.setNotificationAssistantAccessGranted(null, true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + mBinderService.setNotificationAssistantAccessGranted(null, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( c.flattenToString(), 0, true, false); @@ -2231,25 +2238,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetAssistantAccessForUser_nullWithAllowedAssistant() throws Exception { - UserHandle user = UserHandle.of(10); + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 10; + uis.add(ui); + UserHandle user = ui.getUserHandle(); ArrayList<ComponentName> componentList = new ArrayList<>(); ComponentName c = ComponentName.unflattenFromString("package/Component"); componentList.add(c); when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); + when(mUm.getEnabledProfiles(10)).thenReturn(uis); - try { - mBinderService.setNotificationAssistantAccessGrantedForUser( + mBinderService.setNotificationAssistantAccessGrantedForUser( + null, user.getIdentifier(), true); + + verify(mAssistants, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, false); + verify(mAssistants).setUserSet(10, true); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), false, false); + verify(mListeners, never()).setPackageOrComponentEnabled( + any(), anyInt(), anyBoolean(), anyBoolean()); + } + + @Test + public void testSetAssistantAccessForUser_workProfile_nullWithAllowedAssistant() + throws Exception { + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 0; + uis.add(ui); + UserInfo ui10 = new UserInfo(); + ui10.id = 10; + uis.add(ui10); + UserHandle user = ui.getUserHandle(); + ArrayList<ComponentName> componentList = new ArrayList<>(); + ComponentName c = ComponentName.unflattenFromString("package/Component"); + componentList.add(c); + when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); + + mBinderService.setNotificationAssistantAccessGrantedForUser( null, user.getIdentifier(), true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } verify(mAssistants, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), true, false); + verify(mAssistants, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), ui10.id, true, false); + verify(mAssistants).setUserSet(0, true); + verify(mAssistants).setUserSet(10, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), false, false); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), ui10.id, false, false); verify(mListeners, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean()); } @@ -2257,13 +2298,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetDndAccess() throws Exception { ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + + mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( c.getPackageName(), 0, true, true); @@ -2291,6 +2327,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSetAssistantAccess_doesNothingOnLowRam() throws Exception { when(mActivityManager.isLowRamDevice()).thenReturn(true); ComponentName c = ComponentName.unflattenFromString("package/Component"); + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 0; + uis.add(ui); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); + mBinderService.setNotificationAssistantAccessGranted(c, true); verify(mListeners, never()).setPackageOrComponentEnabled( @@ -2320,13 +2362,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true); when(mActivityManager.isLowRamDevice()).thenReturn(true); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationListenerAccessGranted(c, true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + + mBinderService.setNotificationListenerAccessGranted(c, true); verify(mListeners, times(1)).setPackageOrComponentEnabled( c.flattenToString(), 0, true, true); @@ -2341,13 +2378,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true); when(mActivityManager.isLowRamDevice()).thenReturn(true); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationAssistantAccessGranted(c, true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + List<UserInfo> uis = new ArrayList<>(); + UserInfo ui = new UserInfo(); + ui.id = 0; + uis.add(ui); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); + + mBinderService.setNotificationAssistantAccessGranted(c, true); verify(mListeners, never()).setPackageOrComponentEnabled( anyString(), anyInt(), anyBoolean(), anyBoolean()); @@ -2362,13 +2399,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true); when(mActivityManager.isLowRamDevice()).thenReturn(true); ComponentName c = ComponentName.unflattenFromString("package/Component"); - try { - mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true); - } catch (SecurityException e) { - if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { - throw e; - } - } + + mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true); verify(mListeners, never()).setPackageOrComponentEnabled( anyString(), anyInt(), anyBoolean(), anyBoolean()); @@ -4492,6 +4524,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Person person = new Person.Builder() .setName("bubblebot") .build(); + // It needs remote input to be bubble-able + RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); + PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); + Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); + Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", + inputIntent).addRemoteInput(remoteInput) + .build(); // Make it messaging style Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) @@ -4504,6 +4543,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .addMessage("Is it me you're looking for?", SystemClock.currentThreadTimeMillis(), person) ) + .setActions(replyAction) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 45d52195c5fd..5803385e13e2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -79,7 +79,6 @@ import android.view.Gravity; import androidx.test.filters.SmallTest; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; -import com.android.server.wm.TaskRecord.TaskRecordFactory; import org.junit.Before; import org.junit.Test; @@ -330,21 +329,14 @@ public class ActivityStarterTests extends ActivityTestsBase { any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), anyBoolean(), anyBoolean(), any(), any(), any()); - // instrument the stack and task used. - final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TaskRecord task = new TaskBuilder(mSupervisor) - .setCreateStack(false) - .build(); - - // use factory that only returns spy task. - final TaskRecordFactory factory = mock(TaskRecordFactory.class); - TaskRecord.setTaskRecordFactory(factory); - - // return task when created. - doReturn(task).when(factory).create(any(), anyInt(), any(), any(), any(), any()); + // Use factory that only returns spy task. + mockTaskRecordFactory(); if (mockGetLaunchStack) { + // Instrument the stack and task used. + final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // Direct starter to use spy stack. doReturn(stack).when(mRootActivityContainer) .getLaunchStack(any(), any(), any(), anyBoolean()); @@ -707,6 +699,36 @@ public class ActivityStarterTests extends ActivityTestsBase { } /** + * This test ensures that {@link ActivityStarter#setTargetStackAndMoveToFrontIfNeeded} will + * move the existing task to front if the current focused stack doesn't have running task. + */ + @Test + public void testBringTaskToFrontWhenFocusedStackIsFinising() { + // Put 2 tasks in the same stack (simulate the behavior of home stack). + final ActivityRecord activity = new ActivityBuilder(mService) + .setCreateTask(true).build(); + new ActivityBuilder(mService) + .setStack(activity.getActivityStack()) + .setCreateTask(true).build(); + + // Create a top finishing activity. + final ActivityRecord finishingTopActivity = new ActivityBuilder(mService) + .setCreateTask(true).build(); + finishingTopActivity.getActivityStack().moveToFront("finishingTopActivity"); + + assertEquals(finishingTopActivity, mRootActivityContainer.topRunningActivity()); + finishingTopActivity.finishing = true; + + // Launch the bottom task of the target stack. + prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetLaunchStack */) + .setReason("testBringTaskToFrontWhenTopStackIsFinising") + .setIntent(activity.intent) + .execute(); + // The hierarchies of the activity should move to front. + assertEquals(activity, mRootActivityContainer.topRunningActivity()); + } + + /** * This test ensures that when starting an existing single task activity on secondary display * which is not the top focused display, it should deliver new intent to the activity and not * create a new stack. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index afd4ec160aad..09b511a20419 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -41,6 +41,7 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.app.AppOpsManager; import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Context; @@ -71,6 +72,7 @@ import com.android.server.am.PendingIntentController; import com.android.server.appop.AppOpsService; import com.android.server.firewall.IntentFirewall; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.TaskRecord.TaskRecordFactory; import com.android.server.wm.utils.MockTracker; import org.junit.After; @@ -159,6 +161,19 @@ class ActivityTestsBase { } /** + * Delegates task creation to {@link #TaskBuilder} to avoid the dependency of window hierarchy + * when starting activity in unit tests. + */ + void mockTaskRecordFactory() { + final TaskRecord task = new TaskBuilder(mSupervisor).setCreateStack(false).build(); + final TaskRecordFactory factory = mock(TaskRecordFactory.class); + TaskRecord.setTaskRecordFactory(factory); + doReturn(task).when(factory).create(any() /* service */, anyInt() /* taskId */, + any() /* info */, any() /* intent */, any() /* voiceSession */, + any() /* voiceInteractor */); + } + + /** * Builder for creating new activities. */ protected static class ActivityBuilder { @@ -413,12 +428,18 @@ class ActivityTestsBase { ActivityStackSupervisor mTestStackSupervisor; ActivityDisplay mDefaultDisplay; + AppOpsService mAppOpsService; TestActivityTaskManagerService(Context context) { super(context); spyOn(this); mUgmInternal = mock(UriGrantsManagerInternal.class); + mAppOpsService = mock(AppOpsService.class); + + // Make sure permission checks aren't overridden. + doReturn(AppOpsManager.MODE_DEFAULT) + .when(mAppOpsService).noteOperation(anyInt(), anyInt(), anyString()); mSupportsMultiWindow = true; mSupportsMultiDisplay = true; @@ -482,6 +503,11 @@ class ActivityTestsBase { } @Override + AppOpsService getAppOpsService() { + return mAppOpsService; + } + + @Override void updateCpuStats() { } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index bac1ecd5ec31..b2084f88bc43 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -25,7 +25,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -71,10 +70,10 @@ import java.util.Arrays; import java.util.List; /** - * Tests for the {@link ActivityStackSupervisor} class. + * Tests for the {@link RootActivityContainer} class. * * Build/Install/Run: - * atest WmTests:ActivityStackSupervisorTests + * atest WmTests:RootActivityContainerTests */ @MediumTest @Presubmit @@ -399,17 +398,15 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testStartHomeOnAllDisplays() { + mockResolveHomeActivity(); + // Create secondary displays. final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); mRootActivityContainer.addChild(secondDisplay, POSITION_TOP); doReturn(true).when(secondDisplay).supportsSystemDecorations(); // Create mock tasks and other necessary mocks. - TaskBuilder taskBuilder = new TaskBuilder(mService.mStackSupervisor).setCreateStack(false); - final TaskRecord.TaskRecordFactory factory = mock(TaskRecord.TaskRecordFactory.class); - TaskRecord.setTaskRecordFactory(factory); - doAnswer(i -> taskBuilder.build()).when(factory) - .create(any(), anyInt(), any(), any(), any(), any()); + mockTaskRecordFactory(); doReturn(true).when(mRootActivityContainer) .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean()); doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay( @@ -510,6 +507,26 @@ public class RootActivityContainerTests extends ActivityTestsBase { } /** + * Tests that when starting {@link #ResolverActivity} for home, it should use the standard + * activity type (in a new stack) so the order of back stack won't be broken. + */ + @Test + public void testStartResolverActivityForHome() { + final ActivityInfo info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + info.applicationInfo.packageName = "android"; + info.name = ResolverActivity.class.getName(); + doReturn(info).when(mRootActivityContainer).resolveHomeActivity(anyInt(), any()); + mockTaskRecordFactory(); + + mRootActivityContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY); + final ActivityRecord resolverActivity = mRootActivityContainer.topRunningActivity(); + + assertEquals(info, resolverActivity.info); + assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getActivityStack().getActivityType()); + } + + /** * Tests that secondary home should be selected if default home not set. */ @Test @@ -542,13 +559,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSupportMultiDisplay() { - final Intent defaultHomeIntent = mService.getHomeIntent(); - final ActivityInfo aInfoDefault = new ActivityInfo(); - aInfoDefault.name = "fakeHomeActivity"; - aInfoDefault.applicationInfo = new ApplicationInfo(); - aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; - doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), - refEq(defaultHomeIntent)); + mockResolveHomeActivity(); final List<ResolveInfo> resolutions = new ArrayList<>(); doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); @@ -575,13 +586,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testResolveSecondaryHomeActivityWhenDefaultHomeSupportMultiDisplay() { - final Intent homeIntent = mService.getHomeIntent(); - final ActivityInfo aInfoDefault = new ActivityInfo(); - aInfoDefault.name = "fakeHomeActivity"; - aInfoDefault.applicationInfo = new ApplicationInfo(); - aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; - doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), - refEq(homeIntent)); + final ActivityInfo aInfoDefault = mockResolveHomeActivity(); final List<ResolveInfo> resolutions = new ArrayList<>(); final ResolveInfo infoFake1 = new ResolveInfo(); @@ -612,13 +617,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() { - final Intent homeIntent = mService.getHomeIntent(); - final ActivityInfo aInfoDefault = new ActivityInfo(); - aInfoDefault.name = "fakeHomeActivity"; - aInfoDefault.applicationInfo = new ApplicationInfo(); - aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; - doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), - refEq(homeIntent)); + mockResolveHomeActivity(); final List<ResolveInfo> resolutions = new ArrayList<>(); final ResolveInfo infoFake1 = new ResolveInfo(); @@ -646,4 +645,19 @@ public class RootActivityContainerTests extends ActivityTestsBase { resolvedInfo.first.applicationInfo.packageName); assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name); } + + /** + * Mock {@link RootActivityContainerTests#resolveHomeActivity} for returning consistent activity + * info for test cases (the original implementation will resolve from the real package manager). + */ + private ActivityInfo mockResolveHomeActivity() { + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(homeIntent)); + return aInfoDefault; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 715353e5d980..3a8d3b74c08f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -476,4 +476,47 @@ public class WindowStateTests extends WindowTestsBase { root.prepareWindowToDisplayDuringRelayout(wasVisible /*wasVisible*/); verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); } + + @Test + public void testVisibilityChangeSwitchUser() { + final WindowState window = createWindow(null, TYPE_APPLICATION, "app"); + window.mHasSurface = true; + window.setShowToOwnerOnlyLocked(true); + + mWm.mCurrentUserId = 1; + window.switchUser(); + assertFalse(window.isVisible()); + assertFalse(window.isVisibleByPolicy()); + + mWm.mCurrentUserId = 0; + window.switchUser(); + assertTrue(window.isVisible()); + assertTrue(window.isVisibleByPolicy()); + } + + @Test + public void testGetTransformationMatrix() { + synchronized (mWm.mGlobalLock) { + final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); + win0.getFrameLw().offsetTo(1, 0); + + final DisplayContent dc = createNewDisplay(); + dc.reparentDisplayContent(win0, win0.getSurfaceControl()); + dc.updateLocation(win0, 2, 0); + + final float[] values = new float[9]; + final Matrix matrix = new Matrix(); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1"); + win1.mHasSurface = true; + win1.mSurfaceControl = mock(SurfaceControl.class); + win1.getFrameLw().offsetTo(3, 0); + win1.updateSurfacePosition(t); + win1.getTransformationMatrix(values, matrix); + + matrix.getValues(values); + assertEquals(6f, values[Matrix.MTRANS_X], 0f); + assertEquals(0f, values[Matrix.MTRANS_Y], 0f); + } + } } diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 4b33e165bfeb..77866279a751 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -27,6 +27,7 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOU import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_DOZE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_START; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_FOREGROUND_SERVICE_START; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN; @@ -238,6 +239,11 @@ public class AppStandbyController { long mUnexemptedSyncScheduledTimeoutMillis; /** Maximum time a system interaction should keep the buckets elevated. */ long mSystemInteractionTimeoutMillis; + /** + * Maximum time a foreground service start should keep the buckets elevated if the service + * start is the first usage of the app + */ + long mInitialForegroundServiceStartTimeoutMillis; /** The length of time phone must be charging before considered stable enough to run jobs */ long mStableChargingThresholdMillis; @@ -877,7 +883,8 @@ public class AppStandbyController { || event.mEventType == UsageEvents.Event.USER_INTERACTION || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN || event.mEventType == UsageEvents.Event.SLICE_PINNED - || event.mEventType == UsageEvents.Event.SLICE_PINNED_PRIV)) { + || event.mEventType == UsageEvents.Event.SLICE_PINNED_PRIV + || event.mEventType == UsageEvents.Event.FOREGROUND_SERVICE_START)) { final AppUsageHistory appHistory = mAppIdleHistory.getAppUsageHistory( event.mPackage, userId, elapsedRealtime); @@ -898,6 +905,13 @@ public class AppStandbyController { STANDBY_BUCKET_ACTIVE, subReason, 0, elapsedRealtime + mSystemInteractionTimeoutMillis); nextCheckTime = mSystemInteractionTimeoutMillis; + } else if (event.mEventType == UsageEvents.Event.FOREGROUND_SERVICE_START) { + // Only elevate bucket if this is the first usage of the app + if (prevBucket != STANDBY_BUCKET_NEVER) return; + mAppIdleHistory.reportUsage(appHistory, event.mPackage, + STANDBY_BUCKET_ACTIVE, subReason, + 0, elapsedRealtime + mInitialForegroundServiceStartTimeoutMillis); + nextCheckTime = mInitialForegroundServiceStartTimeoutMillis; } else { mAppIdleHistory.reportUsage(appHistory, event.mPackage, STANDBY_BUCKET_ACTIVE, subReason, @@ -930,6 +944,8 @@ public class AppStandbyController { case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN; case UsageEvents.Event.SLICE_PINNED: return REASON_SUB_USAGE_SLICE_PINNED; case UsageEvents.Event.SLICE_PINNED_PRIV: return REASON_SUB_USAGE_SLICE_PINNED_PRIV; + case UsageEvents.Event.FOREGROUND_SERVICE_START: + return REASON_SUB_USAGE_FOREGROUND_SERVICE_START; default: return 0; } } @@ -1509,6 +1525,9 @@ public class AppStandbyController { pw.print(" mSystemInteractionTimeoutMillis="); TimeUtils.formatDuration(mSystemInteractionTimeoutMillis, pw); pw.println(); + pw.print(" mInitialForegroundServiceStartTimeoutMillis="); + TimeUtils.formatDuration(mInitialForegroundServiceStartTimeoutMillis, pw); + pw.println(); pw.print(" mPredictionTimeoutMillis="); TimeUtils.formatDuration(mPredictionTimeoutMillis, pw); @@ -1848,6 +1867,8 @@ public class AppStandbyController { "unexempted_sync_scheduled_duration"; private static final String KEY_SYSTEM_INTERACTION_HOLD_DURATION = "system_interaction_duration"; + private static final String KEY_INITIAL_FOREGROUND_SERVICE_START_HOLD_DURATION = + "initial_foreground_service_start_duration"; private static final String KEY_STABLE_CHARGING_THRESHOLD = "stable_charging_threshold"; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR; public static final long DEFAULT_NOTIFICATION_TIMEOUT = 12 * ONE_HOUR; @@ -1859,6 +1880,7 @@ public class AppStandbyController { public static final long DEFAULT_EXEMPTED_SYNC_START_TIMEOUT = 10 * ONE_MINUTE; public static final long DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT = 10 * ONE_MINUTE; public static final long DEFAULT_STABLE_CHARGING_THRESHOLD = 10 * ONE_MINUTE; + public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT = 30 * ONE_MINUTE; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -1964,6 +1986,12 @@ public class AppStandbyController { mSystemInteractionTimeoutMillis = mParser.getDurationMillis( KEY_SYSTEM_INTERACTION_HOLD_DURATION, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYSTEM_INTERACTION_TIMEOUT); + + mInitialForegroundServiceStartTimeoutMillis = mParser.getDurationMillis( + KEY_INITIAL_FOREGROUND_SERVICE_START_HOLD_DURATION, + COMPRESS_TIME ? ONE_MINUTE : + DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT); + mStableChargingThresholdMillis = mParser.getDurationMillis( KEY_STABLE_CHARGING_THRESHOLD, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_STABLE_CHARGING_THRESHOLD); diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index a814c03ff9ad..5cd46ca936a7 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -301,4 +301,9 @@ interface ITelecomService { void setTestAutoModeApp(String packageName); + /** + * @see TelecomServiceImpl#setTestDefaultDialer + */ + void setTestDefaultDialer(in String packageName); + } diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index 785d7aea2a6f..9a8d7cd35cb0 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -140,6 +140,10 @@ public class UiccSlotInfo implements Parcelable { return mIsEuicc; } + /** + * Returns the ICCID of a the UICC in the given slot, or the EID if it is an eUICC. Note that if + * the value is unavailble this will return null. + */ public String getCardId() { return mCardId; } diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp index 1a91f52bc6cf..d80c2e742fae 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -290,8 +290,14 @@ TestResults::OnTestStatus(TestStatus& status) m_currentAction->target->name.c_str(), className.c_str(), testName.c_str(), g_escapeEndColor); - string stack = get_bundle_string(results, &found, "stack", NULL); - if (found) { + bool stackFound; + string stack = get_bundle_string(results, &stackFound, "stack", NULL); + if (status.has_logcat()) { + const string logcat = status.logcat(); + if (logcat.length() > 0) { + printf("%s\n", logcat.c_str()); + } + } else if (stackFound) { printf("%s\n", stack.c_str()); } } |