diff options
206 files changed, 7471 insertions, 6021 deletions
diff --git a/Android.bp b/Android.bp index c6f9362b4919..59530079d4ad 100644 --- a/Android.bp +++ b/Android.bp @@ -547,6 +547,7 @@ java_library { exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"], aidl: { generate_get_transaction_name: true, + local_include_dirs: ["media/aidl"], }, dxflags: [ "--core-library", @@ -583,6 +584,7 @@ java_library { // in favor of an API stubs dependency in java_library "framework" below. "mimemap", "mediatranscoding_aidl_interface-java", + "soundtrigger_middleware-aidl-java", ], // For backwards compatibility. stem: "framework", diff --git a/ApiDocs.bp b/ApiDocs.bp index ca921ff97c35..c82fee0fe8ac 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -83,6 +83,10 @@ stubs_defaults { merge_annotations_dirs: [ "metalava-manual", ], + // TODO(b/169090544): remove below aidl includes. + aidl: { + local_include_dirs: ["media/aidl"], + }, } droidstubs { @@ -150,6 +154,10 @@ doc_defaults { ":current-support-api", ":current-androidx-api", ], + // TODO(b/169090544): remove below aidl includes. + aidl: { + local_include_dirs: ["media/aidl"], + }, } doc_defaults { diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index fc5efc6e03ac..7a8d1a1caf25 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -11,6 +11,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp libs/input/ services/core/jni/ services/incremental/ + tools/ [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} diff --git a/StubLibraries.bp b/StubLibraries.bp index bb6538739c49..852fcc6128c4 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -51,9 +51,12 @@ stubs_defaults { ":android_icu4j_public_api_files", "**/package.html", ], - // TODO(b/147699819): remove below aidl includes. + // TODO(b/147699819, b/169090544): remove below aidl includes. aidl: { - local_include_dirs: ["telephony/java"], + local_include_dirs: [ + "telephony/java", + "media/aidl", + ], }, libs: ["framework-internal-utils"], installable: false, @@ -488,29 +491,3 @@ java_library_static { ":hwbinder-stubs-docs", ], } - -///////////////////////////////////////////////////////////////////// -// api/*-current.txt files for use by modules in other directories -// like the CTS test -///////////////////////////////////////////////////////////////////// - -filegroup { - name: "frameworks-base-api-current.txt", - srcs: [ - "api/current.txt", - ], -} - -filegroup { - name: "frameworks-base-api-system-current.txt", - srcs: [ - "api/system-current.txt", - ], -} - -filegroup { - name: "frameworks-base-api-system-removed.txt", - srcs: [ - "api/system-removed.txt", - ], -} diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index b638fef36a9e..4512d77e650e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2590,13 +2590,14 @@ public class JobSchedulerService extends com.android.server.SystemService // job that runs one of the app's services, as well as verifying that the // named service properly requires the BIND_JOB_SERVICE permission private void enforceValidJobRequest(int uid, JobInfo job) { - final IPackageManager pm = AppGlobals.getPackageManager(); + final PackageManager pm = getContext() + .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0) + .getPackageManager(); final ComponentName service = job.getService(); try { ServiceInfo si = pm.getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - UserHandle.getUserId(uid)); + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); if (si == null) { throw new IllegalArgumentException("No such service " + service); } @@ -2608,8 +2609,10 @@ public class JobSchedulerService extends com.android.server.SystemService throw new IllegalArgumentException("Scheduled service " + service + " does not require android.permission.BIND_JOB_SERVICE permission"); } - } catch (RemoteException e) { - // Can't happen; the Package Manager is in this same process + } catch (NameNotFoundException e) { + throw new IllegalArgumentException( + "Tried to schedule job for non-existent package: " + + service.getPackageName()); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java index 47ebf32fa875..999c53fb7daf 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java @@ -18,16 +18,15 @@ package com.android.server.job.controllers; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.AppGlobals; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.net.Uri; -import android.os.RemoteException; import android.os.UserHandle; import android.util.IndentingPrintWriter; import android.util.Log; @@ -116,10 +115,15 @@ public class ComponentController extends StateController { ServiceInfo si = mServiceInfoCache.get(userId, service); if (si == null) { try { - si = AppGlobals.getPackageManager().getServiceInfo( - service, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); - } catch (RemoteException e) { - throw new RuntimeException(e); + // createContextAsUser may potentially be expensive + // TODO: cache user context or improve ContextImpl implementation if this becomes + // a problem + si = mContext.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName()); + return null; } mServiceInfoCache.add(userId, service, si); } diff --git a/api/Android.bp b/api/Android.bp index 54ff82c97e17..54031da6e203 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -1,7 +1,45 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_visibility: ["//visibility:private"], +} + +// *-current.txt files for use by modules in other directories like cts +filegroup { + name: "frameworks-base-api-current.txt", + srcs: ["current.txt"], + visibility: ["//visibility:public"], +} + +filegroup { + name: "frameworks-base-api-system-current.txt", + srcs: ["system-current.txt"], + visibility: ["//visibility:public"], +} + +filegroup { + name: "frameworks-base-api-system-removed.txt", + srcs: ["system-removed.txt"], + visibility: ["//visibility:public"], +} + genrule { name: "current-api-xml", tools: ["metalava"], srcs: ["current.txt"], out: ["current.api"], cmd: "$(location metalava) --no-banner -convert2xmlnostrip $(in) $(out)", + visibility: ["//visibility:public"], } diff --git a/api/current.txt b/api/current.txt index 4590325cb771..c3219c902893 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6138,6 +6138,7 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR; field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000 field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000 + field public static final int FLAG_MUTABLE = 33554432; // 0x2000000 field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000 field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000 field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000 @@ -14292,6 +14293,7 @@ package android.graphics { public final class BlurShader extends android.graphics.Shader { ctor public BlurShader(float, float, @Nullable android.graphics.Shader); + ctor public BlurShader(float, float, @Nullable android.graphics.Shader, @NonNull android.graphics.Shader.TileMode); } public class Camera { @@ -15195,6 +15197,20 @@ package android.graphics { ctor public PaintFlagsDrawFilter(int, int); } + public final class ParcelableColorSpace extends android.graphics.ColorSpace implements android.os.Parcelable { + ctor public ParcelableColorSpace(@NonNull android.graphics.ColorSpace); + method public int describeContents(); + method @NonNull public float[] fromXyz(@NonNull float[]); + method @NonNull public android.graphics.ColorSpace getColorSpace(); + method public float getMaxValue(int); + method public float getMinValue(int); + method public static boolean isParcelable(@NonNull android.graphics.ColorSpace); + method public boolean isWideGamut(); + method @NonNull public float[] toXyz(@NonNull float[]); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.graphics.ParcelableColorSpace> CREATOR; + } + public class Path { ctor public Path(); ctor public Path(@Nullable android.graphics.Path); @@ -15630,6 +15646,7 @@ package android.graphics { public enum Shader.TileMode { enum_constant public static final android.graphics.Shader.TileMode CLAMP; + enum_constant public static final android.graphics.Shader.TileMode DECAL; enum_constant public static final android.graphics.Shader.TileMode MIRROR; enum_constant public static final android.graphics.Shader.TileMode REPEAT; } @@ -32009,9 +32026,15 @@ package android.net.wifi.hotspot2.pps { method public int describeContents(); method public String getFqdn(); method public String getFriendlyName(); + method @Nullable public long[] getMatchAllOis(); + method @Nullable public long[] getMatchAnyOis(); + method @Nullable public String[] getOtherHomePartners(); method public long[] getRoamingConsortiumOis(); method public void setFqdn(String); method public void setFriendlyName(String); + method public void setMatchAllOis(@Nullable long[]); + method public void setMatchAnyOis(@Nullable long[]); + method public void setOtherHomePartners(@Nullable String[]); method public void setRoamingConsortiumOis(long[]); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR; diff --git a/api/system-current.txt b/api/system-current.txt index c4f9067b507d..7b572741d2a8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4399,6 +4399,8 @@ package android.media { } public static final class MediaTranscodeManager.TranscodingRequest { + method public int getClientPid(); + method public int getClientUid(); method @NonNull public android.net.Uri getDestinationUri(); method public int getPriority(); method @NonNull public android.net.Uri getSourceUri(); @@ -4409,6 +4411,8 @@ package android.media { public static final class MediaTranscodeManager.TranscodingRequest.Builder { ctor public MediaTranscodeManager.TranscodingRequest.Builder(); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); diff --git a/api/test-current.txt b/api/test-current.txt index 5082c57cd221..128e84f59048 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -997,6 +997,7 @@ package android.content.pm { method public void setEnableRollback(boolean, int); method @RequiresPermission("android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS") public void setGrantedRuntimePermissions(String[]); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex(); + method public void setInstallAsInstantApp(boolean); method public void setInstallerPackageName(@Nullable String); method public void setRequestDowngrade(boolean); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged(); @@ -1852,6 +1853,8 @@ package android.media { } public static final class MediaTranscodeManager.TranscodingRequest { + method public int getClientPid(); + method public int getClientUid(); method @NonNull public android.net.Uri getDestinationUri(); method public int getPriority(); method @NonNull public android.net.Uri getSourceUri(); @@ -1862,6 +1865,8 @@ package android.media { public static final class MediaTranscodeManager.TranscodingRequest.Builder { ctor public MediaTranscodeManager.TranscodingRequest.Builder(); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 9c796128df84..046145f90808 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -482,6 +482,8 @@ status_t BootAnimation::readyToRun() { mFlingerSurface = s; mTargetInset = -1; + projectSceneToWindow(); + // Register a display event receiver mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>(); status_t status = mDisplayEventReceiver->initCheck(); @@ -493,6 +495,16 @@ status_t BootAnimation::readyToRun() { return NO_ERROR; } +void BootAnimation::projectSceneToWindow() { + glViewport(0, 0, mWidth, mHeight); + glScissor(0, 0, mWidth, mHeight); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + void BootAnimation::resizeSurface(int newWidth, int newHeight) { // We assume this function is called on the animation thread. if (newWidth == mWidth && newHeight == mHeight) { @@ -517,8 +529,8 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) { SLOGE("Can't make the new surface current. Error %d", eglGetError()); return; } - glViewport(0, 0, mWidth, mHeight); - glScissor(0, 0, mWidth, mHeight); + + projectSceneToWindow(); mSurface = surface; } @@ -799,6 +811,37 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) { return status; } +void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth, + const int frameHeight, const Animation::Part& part, + const int fadedFramesCount) { + glEnable(GL_BLEND); + glEnableClientState(GL_VERTEX_ARRAY); + glDisable(GL_TEXTURE_2D); + // avoid creating a hole due to mixing result alpha with GL_REPLACE texture + glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + + const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount; + glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha); + + const float frameStartX = static_cast<float>(frameLeft); + const float frameStartY = static_cast<float>(frameBottom); + const float frameEndX = frameStartX + frameWidth; + const float frameEndY = frameStartY + frameHeight; + const GLfloat frameRect[] = { + frameStartX, frameStartY, + frameEndX, frameStartY, + frameEndX, frameEndY, + frameStartX, frameEndY + }; + glVertexPointer(2, GL_FLOAT, 0, frameRect); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); + glDisableClientState(GL_VERTEX_ARRAY); + glDisable(GL_BLEND); +} + void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) { glEnable(GL_BLEND); // Allow us to draw on top of the animation glBindTexture(GL_TEXTURE_2D, font.texture.name); @@ -890,23 +933,34 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { int height = 0; int count = 0; int pause = 0; + int framesToFadeCount = 0; char path[ANIM_ENTRY_NAME_MAX]; char color[7] = "000000"; // default to black if unspecified char clockPos1[TEXT_POS_LEN_MAX + 1] = ""; char clockPos2[TEXT_POS_LEN_MAX + 1] = ""; - char pathType; + + int nextReadPos; + if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) { // SLOGD("> w=%d, h=%d, fps=%d", width, height, fps); animation.width = width; animation.height = height; animation.fps = fps; - } else if (sscanf(l, " %c %d %d %" STRTO(ANIM_PATH_MAX) "s #%6s %16s %16s", - &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) { - //SLOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s", - // pathType, count, pause, path, color, clockPos1, clockPos2); + } else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n", + &pathType, &count, &pause, path, &nextReadPos) >= 4) { + if (pathType == 'f') { + sscanf(l + nextReadPos, " %d #%6s %16s %16s", &framesToFadeCount, color, clockPos1, + clockPos2); + } else { + sscanf(l + nextReadPos, " #%6s %16s %16s", color, clockPos1, clockPos2); + } + // SLOGD("> type=%c, count=%d, pause=%d, path=%s, framesToFadeCount=%d, color=%s, " + // "clockPos1=%s, clockPos2=%s", + // pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2); Animation::Part part; part.playUntilComplete = pathType == 'c'; + part.framesToFadeCount = framesToFadeCount; part.count = count; part.pause = pause; part.path = path; @@ -925,6 +979,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { // SLOGD("> SYSTEM"); Animation::Part part; part.playUntilComplete = false; + part.framesToFadeCount = 0; part.count = 1; part.pause = 0; part.audioData = nullptr; @@ -1121,12 +1176,19 @@ bool BootAnimation::movie() { return false; } +bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part, const int fadedFramesCount) { + // stop playing only if it is time to exit and it's a partial part which has been faded out + return exitPending() && !part.playUntilComplete && fadedFramesCount >= part.framesToFadeCount; +} + bool BootAnimation::playAnimation(const Animation& animation) { const size_t pcount = animation.parts.size(); nsecs_t frameDuration = s2ns(1) / animation.fps; SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime()); + + int fadedFramesCount = 0; for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); const size_t fcount = part.frames.size(); @@ -1140,10 +1202,9 @@ bool BootAnimation::playAnimation(const Animation& animation) { continue; //to next part } - for (int r=0 ; !part.count || r<part.count ; r++) { - // Exit any non playuntil complete parts immediately - if(exitPending() && !part.playUntilComplete) - break; + // process the part not only while the count allows but also if already fading + for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) { + if (shouldStopPlayingPart(part, fadedFramesCount)) break; mCallbacks->playPart(i, part, r); @@ -1153,7 +1214,9 @@ bool BootAnimation::playAnimation(const Animation& animation) { part.backgroundColor[2], 1.0f); - for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) { + for (size_t j=0 ; j<fcount ; j++) { + if (shouldStopPlayingPart(part, fadedFramesCount)) break; + processDisplayEvents(); const int animationX = (mWidth - animation.width) / 2; @@ -1192,11 +1255,22 @@ bool BootAnimation::playAnimation(const Animation& animation) { } // specify the y center as ceiling((mHeight - frame.trimHeight) / 2) // which is equivalent to mHeight - (yc + frame.trimHeight) - glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight), - 0, frame.trimWidth, frame.trimHeight); + const int frameDrawY = mHeight - (yc + frame.trimHeight); + glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight); + + // if the part hasn't been stopped yet then continue fading if necessary + if (exitPending() && part.hasFadingPhase()) { + fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part, + ++fadedFramesCount); + if (fadedFramesCount >= part.framesToFadeCount) { + fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading + } + } + if (mClockEnabled && mTimeIsAccurate && validClock(part)) { drawClock(animation.clockFont, part.clockPosX, part.clockPosY); } + handleViewport(frameDuration); eglSwapBuffers(mDisplay, mSurface); @@ -1221,11 +1295,11 @@ bool BootAnimation::playAnimation(const Animation& animation) { usleep(part.pause * ns2us(frameDuration)); - // For infinite parts, we've now played them at least once, so perhaps exit - if(exitPending() && !part.count && mCurrentInset >= mTargetInset) - break; + if (exitPending() && !part.count && mCurrentInset >= mTargetInset && + !part.hasFadingPhase()) { + break; // exit the infinite non-fading part when it has been played at least once + } } - } // Free textures created for looping parts now that the animation is done. diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 9e6e4aa42f1c..4699cfe2ac2d 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -19,6 +19,7 @@ #include <vector> #include <queue> +#include <climits> #include <stdint.h> #include <sys/types.h> @@ -43,6 +44,8 @@ class SurfaceControl; class BootAnimation : public Thread, public IBinder::DeathRecipient { public: + static constexpr int MAX_FADED_FRAMES_COUNT = std::numeric_limits<int>::max(); + struct Texture { GLint w; GLint h; @@ -82,10 +85,15 @@ public: String8 trimData; SortedVector<Frame> frames; bool playUntilComplete; + int framesToFadeCount; float backgroundColor[3]; uint8_t* audioData; int audioLength; Animation* animation; + + bool hasFadingPhase() const { + return !playUntilComplete && framesToFadeCount > 0; + } }; int fps; int width; @@ -160,6 +168,8 @@ private: bool movie(); void drawText(const char* str, const Font& font, bool bold, int* x, int* y); void drawClock(const Font& font, const int xPos, const int yPos); + void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight, + const Animation::Part& part, int fadedFramesCount); bool validClock(const Animation::Part& part); Animation* loadAnimation(const String8&); bool playAnimation(const Animation&); @@ -172,7 +182,9 @@ private: EGLConfig getEglConfig(const EGLDisplay&); ui::Size limitSurfaceSize(int width, int height) const; void resizeSurface(int newWidth, int newHeight); + void projectSceneToWindow(); + bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount); void checkExit(); void handleViewport(nsecs_t timestep); diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md index 5946515aa263..f9b83c957d5b 100644 --- a/cmds/bootanimation/FORMAT.md +++ b/cmds/bootanimation/FORMAT.md @@ -30,14 +30,20 @@ The first line defines the general parameters of the animation: It is followed by a number of rows of the form: - TYPE COUNT PAUSE PATH [#RGBHEX [CLOCK1 [CLOCK2]]] + TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]] * **TYPE:** a single char indicating what type of animation segment this is: + `p` -- this part will play unless interrupted by the end of the boot + `c` -- this part will play to completion, no matter what + + `f` -- same as `p` but in addition the specified number of frames is being faded out while + continue playing. Only the first interrupted `f` part is faded out, other subsequent `f` + parts are skipped * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete * **PAUSE:** number of FRAMES to delay after this part ends * **PATH:** directory in which to find the frames for this part (e.g. `part0`) + * **FADE:** _(ONLY FOR `f` TYPE)_ number of frames to fade out when interrupted where `0` means + _immediately_ which makes `f ... 0` behave like `p` and doesn't count it as a fading + part * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB` * **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches): + If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate diff --git a/cmds/statsd/src/condition/ConditionTimer.h b/cmds/statsd/src/condition/ConditionTimer.h index 442bc11934fe..1fbe25279736 100644 --- a/cmds/statsd/src/condition/ConditionTimer.h +++ b/cmds/statsd/src/condition/ConditionTimer.h @@ -36,7 +36,7 @@ class ConditionTimer { public: explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) { if (initCondition) { - mLastConditionTrueTimestampNs = bucketStartNs; + mLastConditionChangeTimestampNs = bucketStartNs; } }; @@ -44,21 +44,46 @@ public: // When a new bucket is created, this value will be reset to 0. int64_t mTimerNs = 0; - // Last elapsed real timestamp when condition turned to true - // When a new bucket is created and the condition is true, then the timestamp is set - // to be the bucket start timestamp. - int64_t mLastConditionTrueTimestampNs = 0; + // Last elapsed real timestamp when condition changed. + int64_t mLastConditionChangeTimestampNs = 0; bool mCondition = false; int64_t newBucketStart(int64_t nextBucketStartNs) { if (mCondition) { - mTimerNs += (nextBucketStartNs - mLastConditionTrueTimestampNs); - mLastConditionTrueTimestampNs = nextBucketStartNs; + // Normally, the next bucket happens after the last condition + // change. In this case, add the time between the condition becoming + // true to the next bucket start time. + // Otherwise, the next bucket start time is before the last + // condition change time, this means that the condition was false at + // the bucket boundary before the condition became true, so the + // timer should not get updated and the last condition change time + // remains as is. + if (nextBucketStartNs >= mLastConditionChangeTimestampNs) { + mTimerNs += (nextBucketStartNs - mLastConditionChangeTimestampNs); + mLastConditionChangeTimestampNs = nextBucketStartNs; + } + } else if (mLastConditionChangeTimestampNs > nextBucketStartNs) { + // The next bucket start time is before the last condition change + // time, this means that the condition was true at the bucket + // boundary before the condition became false, so adjust the timer + // to match how long the condition was true to the bucket boundary. + // This means remove the amount the condition stayed true in the + // next bucket from the current bucket. + mTimerNs -= (mLastConditionChangeTimestampNs - nextBucketStartNs); } int64_t temp = mTimerNs; mTimerNs = 0; + + if (!mCondition && (mLastConditionChangeTimestampNs > nextBucketStartNs)) { + // The next bucket start time is before the last condition change + // time, this means that the condition was true at the bucket + // boundary and remained true in the next bucket up to the condition + // change to false, so adjust the timer to match how long the + // condition stayed true in the next bucket (now the current bucket). + mTimerNs = mLastConditionChangeTimestampNs - nextBucketStartNs; + } return temp; } @@ -67,11 +92,10 @@ public: return; } mCondition = newCondition; - if (newCondition) { - mLastConditionTrueTimestampNs = timestampNs; - } else { - mTimerNs += (timestampNs - mLastConditionTrueTimestampNs); + if (newCondition == false) { + mTimerNs += (timestampNs - mLastConditionChangeTimestampNs); } + mLastConditionChangeTimestampNs = timestampNs; } FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False); @@ -80,4 +104,4 @@ public: } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 39ae9a47f2bf..3d57cfe318c5 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -301,7 +301,6 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long)(NanoToMillis(dropEvent.dropTimeNs))); - ; protoOutput->end(dropEventToken); } protoOutput->end(wrapperToken); @@ -346,8 +345,11 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); } - // only write the condition timer value if the metric has a condition. - if (mConditionTrackerIndex >= 0) { + // We only write the condition timer value if the metric has a + // condition and/or is sliced by state. + // If the metric is sliced by state, the condition timer value is + // also sliced by state to reflect time spent in that state. + if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) { protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS, (long long)bucket.mConditionTrueNs); } @@ -454,6 +456,8 @@ void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) // Let condition timer know of new active state. mConditionTimer.onConditionChanged(mIsActive, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mIsActive, eventTimeNs); } void ValueMetricProducer::onConditionChangedLocked(const bool condition, @@ -476,6 +480,8 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); mCondition = ConditionState::kUnknown; mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); return; } @@ -517,6 +523,29 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, flushIfNeededLocked(eventTimeNs); mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); +} + +void ValueMetricProducer::updateCurrentSlicedBucketConditionTimers(bool newCondition, + int64_t eventTimeNs) { + if (mSlicedStateAtoms.empty()) { + return; + } + + // Utilize the current state key of each DimensionsInWhat key to determine + // which condition timers to update. + // + // Assumes that the MetricDimensionKey exists in `mCurrentSlicedBucket`. + bool inPulledData; + for (const auto& [dimensionInWhatKey, dimensionInWhatInfo] : mCurrentBaseInfo) { + // If the new condition is true, turn ON the condition timer only if + // the DimensionInWhat key was present in the pulled data. + inPulledData = dimensionInWhatInfo.hasCurrentState; + mCurrentSlicedBucket[MetricDimensionKey(dimensionInWhatKey, + dimensionInWhatInfo.currentState)] + .conditionTimer.onConditionChanged(newCondition && inPulledData, eventTimeNs); + } } void ValueMetricProducer::prepareFirstBucketLocked() { @@ -618,8 +647,8 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log // 2. A superset of the current mStateChangePrimaryKey // was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys) // then we need to reset the base. - for (auto& slice : mCurrentSlicedBucket) { - const auto& whatKey = slice.first.getDimensionKeyInWhat(); + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + const auto& whatKey = metricDimensionKey.getDimensionKeyInWhat(); bool presentInPulledData = mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end(); if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) { @@ -627,6 +656,12 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log for (auto& baseInfo : it->second.baseInfos) { baseInfo.hasBase = false; } + // Set to false when DimensionInWhat key is not present in a pull. + // Used in onMatchedLogEventInternalLocked() to ensure the condition + // timer is turned on the next pull when data is present. + it->second.hasCurrentState = false; + // Turn OFF condition timer for keys not present in pulled data. + currentValueBucket.conditionTimer.onConditionChanged(false, eventElapsedTimeNs); } } mMatchedMetricDimensionKeys.clear(); @@ -789,21 +824,26 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( return; } - DimensionsInWhatInfo& dimensionsInWhatInfo = mCurrentBaseInfo[whatKey]; + const auto& returnVal = + mCurrentBaseInfo.emplace(whatKey, DimensionsInWhatInfo(getUnknownStateKey())); + DimensionsInWhatInfo& dimensionsInWhatInfo = returnVal.first->second; + const HashableDimensionKey oldStateKey = dimensionsInWhatInfo.currentState; vector<BaseInfo>& baseInfos = dimensionsInWhatInfo.baseInfos; if (baseInfos.size() < mFieldMatchers.size()) { VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); baseInfos.resize(mFieldMatchers.size()); } + // Ensure we turn on the condition timer in the case where dimensions + // were missing on a previous pull due to a state change. + bool stateChange = oldStateKey != stateKey; if (!dimensionsInWhatInfo.hasCurrentState) { - dimensionsInWhatInfo.currentState = getUnknownStateKey(); + stateChange = true; dimensionsInWhatInfo.hasCurrentState = true; } // We need to get the intervals stored with the previous state key so we can // close these value intervals. - const auto oldStateKey = dimensionsInWhatInfo.currentState; vector<Interval>& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals; if (intervals.size() < mFieldMatchers.size()) { @@ -916,6 +956,17 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( interval.sampleSize += 1; } + // State change. + if (!mSlicedStateAtoms.empty() && stateChange) { + // Turn OFF the condition timer for the previous state key. + mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)] + .conditionTimer.onConditionChanged(false, eventTimeNs); + + // Turn ON the condition timer for the new state key. + mCurrentSlicedBucket[MetricDimensionKey(whatKey, stateKey)] + .conditionTimer.onConditionChanged(true, eventTimeNs); + } + // Only trigger the tracker if all intervals are correct and we have not skipped the bucket due // to MULTIPLE_BUCKETS_SKIPPED. if (useAnomalyDetection && !multipleBucketsSkipped(calcBucketsForwardCount(eventTimeNs))) { @@ -990,12 +1041,18 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, if (!mCurrentBucketIsSkipped) { bool bucketHasData = false; // The current bucket is large enough to keep. - for (const auto& slice : mCurrentSlicedBucket) { - PastValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second.intervals); - bucket.mConditionTrueNs = conditionTrueDuration; + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + PastValueBucket bucket = + buildPartialBucket(bucketEndTime, currentValueBucket.intervals); + if (!mSlicedStateAtoms.empty()) { + bucket.mConditionTrueNs = + currentValueBucket.conditionTimer.newBucketStart(bucketEndTime); + } else { + bucket.mConditionTrueNs = conditionTrueDuration; + } // it will auto create new vector of ValuebucketInfo if the key is not found. if (bucket.valueIndex.size() > 0) { - auto& bucketList = mPastBuckets[slice.first]; + auto& bucketList = mPastBuckets[metricDimensionKey]; bucketList.push_back(bucket); bucketHasData = true; } @@ -1023,11 +1080,18 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA)); mSkippedBuckets.emplace_back(bucketInGap); } - appendToFullBucket(eventTimeNs > fullBucketEndTimeNs); initCurrentSlicedBucket(nextBucketStartTimeNs); // Update the condition timer again, in case we skipped buckets. mConditionTimer.newBucketStart(nextBucketStartTimeNs); + + // NOTE: Update the condition timers in `mCurrentSlicedBucket` only when slicing + // by state. Otherwise, the "global" condition timer will be used. + if (!mSlicedStateAtoms.empty()) { + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + currentValueBucket.conditionTimer.newBucketStart(nextBucketStartTimeNs); + } + } mCurrentBucketNum += numBucketsForward; } @@ -1069,6 +1133,17 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) interval.seenNewData = false; } + if (obsolete && !mSlicedStateAtoms.empty()) { + // When slicing by state, only delete the MetricDimensionKey when the + // state key in the MetricDimensionKey is not the current state key. + const HashableDimensionKey& dimensionInWhatKey = it->first.getDimensionKeyInWhat(); + const auto& currentBaseInfoItr = mCurrentBaseInfo.find(dimensionInWhatKey); + + if ((currentBaseInfoItr != mCurrentBaseInfo.end()) && + (it->first.getStateValuesKey() == currentBaseInfoItr->second.currentState)) { + obsolete = false; + } + } if (obsolete) { it = mCurrentSlicedBucket.erase(it); } else { @@ -1104,7 +1179,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { // Accumulate partial buckets with current value and then send to anomaly tracker. if (mCurrentFullBucket.size() > 0) { for (const auto& slice : mCurrentSlicedBucket) { - if (hitFullBucketGuardRailLocked(slice.first)) { + if (hitFullBucketGuardRailLocked(slice.first) || slice.second.intervals.empty()) { continue; } // TODO: fix this when anomaly can accept double values @@ -1125,7 +1200,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { // Skip aggregating the partial buckets since there's no previous partial bucket. for (const auto& slice : mCurrentSlicedBucket) { for (auto& tracker : mAnomalyTrackers) { - if (tracker != nullptr) { + if (tracker != nullptr && !slice.second.intervals.empty()) { // TODO: fix this when anomaly can accept double values auto& interval = slice.second.intervals[0]; if (interval.hasValue) { @@ -1139,10 +1214,12 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { } else { // Accumulate partial bucket. for (const auto& slice : mCurrentSlicedBucket) { - // TODO: fix this when anomaly can accept double values - auto& interval = slice.second.intervals[0]; - if (interval.hasValue) { - mCurrentFullBucket[slice.first] += interval.value.long_value; + if (!slice.second.intervals.empty()) { + // TODO: fix this when anomaly can accept double values + auto& interval = slice.second.intervals[0]; + if (interval.hasValue) { + mCurrentFullBucket[slice.first] += interval.value.long_value; + } } } } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 4b2599bdb517..67de214e655c 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -193,8 +193,14 @@ private: // Internal state of an ongoing aggregation bucket. typedef struct CurrentValueBucket { + // If the `MetricDimensionKey` state key is the current state key, then + // the condition timer will be updated later (e.g. condition/state/active + // state change) with the correct condition and time. + CurrentValueBucket() : intervals(), conditionTimer(ConditionTimer(false, 0)) {} // Value information for each value field of the metric. std::vector<Interval> intervals; + // Tracks how long the condition is true. + ConditionTimer conditionTimer; } CurrentValueBucket; // Holds base information for diffing values from one value field. @@ -206,7 +212,10 @@ private: } BaseInfo; // State key and base information for a specific DimensionsInWhat key. - typedef struct { + typedef struct DimensionsInWhatInfo { + DimensionsInWhatInfo(const HashableDimensionKey& stateKey) + : baseInfos(), currentState(stateKey), hasCurrentState(false) { + } std::vector<BaseInfo> baseInfos; // Last seen state value(s). HashableDimensionKey currentState; @@ -252,6 +261,10 @@ private: // Reset diff base and mHasGlobalBase void resetBase(); + // Updates the condition timers in the current sliced bucket when there is a + // condition change or an active state change. + void updateCurrentSlicedBucketConditionTimers(bool newCondition, int64_t eventTimeNs); + static const size_t kBucketSize = sizeof(PastValueBucket{}); const size_t mDimensionSoftLimit; @@ -337,6 +350,11 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary); FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed); FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed); diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp index 5dbf16deb552..2ae57638791e 100644 --- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp +++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp @@ -24,6 +24,8 @@ #include "matchers/EventMatcherWizard.h" #include "metrics_manager_util.h" +using google::protobuf::MessageLite; + namespace android { namespace os { namespace statsd { @@ -419,16 +421,19 @@ bool metricActivationDepsChange(const StatsdConfig& config, return false; } -bool determineEventMetricUpdateStatus(const StatsdConfig& config, const EventMetric& metric, - const unordered_map<int64_t, int>& oldMetricProducerMap, - const vector<sp<MetricProducer>>& oldMetricProducers, - const unordered_map<int64_t, int>& metricToActivationMap, - const set<int64_t>& replacedMatchers, - const set<int64_t>& replacedConditions, - UpdateStatus& updateStatus) { - int64_t id = metric.id(); +bool determineMetricUpdateStatus( + const StatsdConfig& config, const MessageLite& metric, const int64_t metricId, + const MetricType metricType, const set<int64_t>& matcherDependencies, + const set<int64_t>& conditionDependencies, + const ::google::protobuf::RepeatedField<int64_t>& stateDependencies, + const ::google::protobuf::RepeatedPtrField<MetricConditionLink>& conditionLinks, + const unordered_map<int64_t, int>& oldMetricProducerMap, + const vector<sp<MetricProducer>>& oldMetricProducers, + const unordered_map<int64_t, int>& metricToActivationMap, + const set<int64_t>& replacedMatchers, const set<int64_t>& replacedConditions, + const set<int64_t>& replacedStates, UpdateStatus& updateStatus) { // Check if new metric - const auto& oldMetricProducerIt = oldMetricProducerMap.find(id); + const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId); if (oldMetricProducerIt == oldMetricProducerMap.end()) { updateStatus = UPDATE_NEW; return true; @@ -436,41 +441,82 @@ bool determineEventMetricUpdateStatus(const StatsdConfig& config, const EventMet // This is an existing metric, check if it has changed. uint64_t metricHash; - if (!getMetricProtoHash(config, metric, id, metricToActivationMap, metricHash)) { + if (!getMetricProtoHash(config, metric, metricId, metricToActivationMap, metricHash)) { return false; } const sp<MetricProducer> oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second]; - if (oldMetricProducer->getMetricType() != METRIC_TYPE_EVENT || + if (oldMetricProducer->getMetricType() != metricType || oldMetricProducer->getProtoHash() != metricHash) { updateStatus = UPDATE_REPLACE; return true; } - // Metric type and definition are the same. Need to check dependencies to see if they changed. - if (replacedMatchers.find(metric.what()) != replacedMatchers.end()) { + // Take intersections of the matchers/predicates/states that the metric + // depends on with those that have been replaced. If a metric depends on any + // replaced component, it too must be replaced. + set<int64_t> intersection; + set_intersection(matcherDependencies.begin(), matcherDependencies.end(), + replacedMatchers.begin(), replacedMatchers.end(), + inserter(intersection, intersection.begin())); + if (intersection.size() > 0) { + updateStatus = UPDATE_REPLACE; + return true; + } + set_intersection(conditionDependencies.begin(), conditionDependencies.end(), + replacedConditions.begin(), replacedConditions.end(), + inserter(intersection, intersection.begin())); + if (intersection.size() > 0) { + updateStatus = UPDATE_REPLACE; + return true; + } + set_intersection(stateDependencies.begin(), stateDependencies.end(), replacedStates.begin(), + replacedStates.end(), inserter(intersection, intersection.begin())); + if (intersection.size() > 0) { updateStatus = UPDATE_REPLACE; return true; } - if (metric.has_condition()) { - if (replacedConditions.find(metric.condition()) != replacedConditions.end()) { + for (const auto& metricConditionLink : conditionLinks) { + if (replacedConditions.find(metricConditionLink.condition()) != replacedConditions.end()) { updateStatus = UPDATE_REPLACE; return true; } } - if (metricActivationDepsChange(config, metricToActivationMap, id, replacedMatchers)) { + if (metricActivationDepsChange(config, metricToActivationMap, metricId, replacedMatchers)) { updateStatus = UPDATE_REPLACE; return true; } - for (const auto& metricConditionLink : metric.links()) { - if (replacedConditions.find(metricConditionLink.condition()) != replacedConditions.end()) { - updateStatus = UPDATE_REPLACE; - return true; + updateStatus = UPDATE_PRESERVE; + return true; +} + +bool determineAllMetricUpdateStatuses(const StatsdConfig& config, + const unordered_map<int64_t, int>& oldMetricProducerMap, + const vector<sp<MetricProducer>>& oldMetricProducers, + const unordered_map<int64_t, int>& metricToActivationMap, + const set<int64_t>& replacedMatchers, + const set<int64_t>& replacedConditions, + const set<int64_t>& replacedStates, + vector<UpdateStatus>& metricsToUpdate) { + int metricIndex = 0; + for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { + const EventMetric& metric = config.event_metric(i); + set<int64_t> conditionDependencies; + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_EVENT, {metric.what()}, + conditionDependencies, ::google::protobuf::RepeatedField<int64_t>(), + metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; } } - updateStatus = UPDATE_PRESERVE; + // TODO: determine update status for count, gauge, value, duration metrics. return true; } @@ -518,22 +564,16 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 } vector<UpdateStatus> metricsToUpdate(allMetricsCount, UPDATE_UNKNOWN); - int metricIndex = 0; - for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { - newMetricProducerMap[config.event_metric(i).id()] = metricIndex; - if (!determineEventMetricUpdateStatus(config, config.event_metric(i), oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, - metricsToUpdate[metricIndex])) { - return false; - } + if (!determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, replacedMatchers, + replacedConditions, replacedStates, metricsToUpdate)) { + return false; } - // TODO: determine update status for count, gauge, value, duration metrics. - // Now, perform the update. Must iterate the metric types in the same order - metricIndex = 0; + int metricIndex = 0; for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { + newMetricProducerMap[config.event_metric(i).id()] = metricIndex; const EventMetric& metric = config.event_metric(i); switch (metricsToUpdate[metricIndex]) { case UPDATE_PRESERVE: { diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h index 1cd0ce524b31..34d7e9c7de9e 100644 --- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h +++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h @@ -125,23 +125,25 @@ bool updateConditions(const ConfigKey& key, const StatsdConfig& config, std::vector<ConditionState>& conditionCache, std::set<int64_t>& replacedConditions); -// Function to determine if an event metric needs to be updated. Populates updateStatus. +// Function to determine the update status (preserve/replace/new) of all metrics in the config. // [config]: the input StatsdConfig -// [metric]: the current metric to be updated // [oldMetricProducerMap]: metric id to index mapping in the existing MetricsManager // [oldMetricProducers]: stores the existing MetricProducers -// [metricToActivationMap]: map from metric id to metric activation index. -// [replacedMatchers]: set of replaced matcher ids. conditions using these matchers must be replaced +// [metricToActivationMap]: map from metric id to metric activation index +// [replacedMatchers]: set of replaced matcher ids. metrics using these matchers must be replaced +// [replacedConditions]: set of replaced conditions. metrics using these conditions must be replaced +// [replacedStates]: set of replaced state ids. metrics using these states must be replaced // output: -// [updateStatus]: update status for the metric. Will be changed from UPDATE_UNKNOWN after this call +// [metricsToUpdate]: update status of each metric. Will be changed from UPDATE_UNKNOWN // Returns whether the function was successful or not. -bool determineEventMetricUpdateStatus(const StatsdConfig& config, const EventMetric& metric, +bool determineAllMetricUpdateStatuses(const StatsdConfig& config, const unordered_map<int64_t, int>& oldMetricProducerMap, const vector<sp<MetricProducer>>& oldMetricProducers, const unordered_map<int64_t, int>& metricToActivationMap, const set<int64_t>& replacedMatchers, const set<int64_t>& replacedConditions, - UpdateStatus& updateStatus); + const set<int64_t>& replacedStates, + vector<UpdateStatus>& metricsToUpdate); // Update MetricProducers. // input: diff --git a/cmds/statsd/tests/condition/ConditionTimer_test.cpp b/cmds/statsd/tests/condition/ConditionTimer_test.cpp index ea02cd3a5ee1..46dc9a9d381f 100644 --- a/cmds/statsd/tests/condition/ConditionTimer_test.cpp +++ b/cmds/statsd/tests/condition/ConditionTimer_test.cpp @@ -35,11 +35,11 @@ TEST(ConditionTimerTest, TestTimer_Inital_False) { EXPECT_EQ(0, timer.mTimerNs); timer.onConditionChanged(true, ct_start_time + 5); - EXPECT_EQ(ct_start_time + 5, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(ct_start_time + 5, timer.mLastConditionChangeTimestampNs); EXPECT_EQ(true, timer.mCondition); EXPECT_EQ(95, timer.newBucketStart(ct_start_time + 100)); - EXPECT_EQ(ct_start_time + 100, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(ct_start_time + 100, timer.mLastConditionChangeTimestampNs); EXPECT_EQ(true, timer.mCondition); } @@ -51,7 +51,7 @@ TEST(ConditionTimerTest, TestTimer_Inital_True) { EXPECT_EQ(ct_start_time - time_base, timer.newBucketStart(ct_start_time)); EXPECT_EQ(true, timer.mCondition); EXPECT_EQ(0, timer.mTimerNs); - EXPECT_EQ(ct_start_time, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(ct_start_time, timer.mLastConditionChangeTimestampNs); timer.onConditionChanged(false, ct_start_time + 5); EXPECT_EQ(5, timer.mTimerNs); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index b166cc1fe04e..6cf4192b41fd 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -93,6 +93,13 @@ static void assertPastBucketValuesSingleKey( } } +static void assertConditionTimer(const ConditionTimer& conditionTimer, bool condition, + int64_t timerNs, int64_t lastConditionTrueTimestampNs) { + EXPECT_EQ(condition, conditionTimer.mCondition); + EXPECT_EQ(timerNs, conditionTimer.mTimerNs); + EXPECT_EQ(lastConditionTrueTimestampNs, conditionTimer.mLastConditionChangeTimestampNs); +} + } // anonymous namespace class ValueMetricProducerTestHelper { @@ -3967,33 +3974,37 @@ TEST(ValueMetricProducerTest, TestSlicedState) { // Screen state change to ON. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); return true; })) // Screen state change to OFF. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 9)); return true; })) // Screen state change to ON. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); return true; })) // Dump report requested. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); return true; })); @@ -4025,12 +4036,13 @@ TEST(ValueMetricProducerTest, TestSlicedState) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after screen state change kStateUnknown->ON. auto screenEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4040,19 +4052,29 @@ TEST(ValueMetricProducerTest, TestSlicedState) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(0, it->second.intervals.size()); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change ON->OFF. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4061,13 +4083,23 @@ TEST(ValueMetricProducerTest, TestSlicedState) { EXPECT_TRUE(itBase->second.hasCurrentState); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(0, it->second.intervals.size()); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); // Value for dimension, state key {{}, ON} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4076,9 +4108,11 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change OFF->ON. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); @@ -4098,6 +4132,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(12, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, ON} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4106,6 +4142,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, true, 5 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4114,37 +4152,46 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Start dump report and check output. ProtoOutputStream output; std::set<string> strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); EXPECT_TRUE(report.has_value_metrics()); ASSERT_EQ(3, report.value_metrics().data_size()); + // {{}, kStateUnknown} auto data = report.value_metrics().data(0); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, ON} data = report.value_metrics().data(1); ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, OFF} data = report.value_metrics().data(2); ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); } /* @@ -4169,9 +4216,10 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Screen state change to ON. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); return true; })) // Screen state change to VR has no pull because it is in the same @@ -4183,17 +4231,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Screen state change to OFF. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); return true; })) // Dump report requested. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); return true; })); @@ -4236,12 +4286,13 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after screen state change kStateUnknown->ON. auto screenEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4251,20 +4302,29 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.long_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change ON->VR. // Both ON and VR are in the same state group, so the base should not change. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_VR); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4274,20 +4334,29 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change VR->ON. // Both ON and VR are in the same state group, so the base should not change. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4297,19 +4366,28 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change VR->OFF. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4319,13 +4397,22 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOffGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOffGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.long_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, ON GROUP} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), it->first.getStateValuesKey().getValues()[0].mValue.long_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(16, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4334,37 +4421,46 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Start dump report and check output. ProtoOutputStream output; std::set<string> strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); EXPECT_TRUE(report.has_value_metrics()); ASSERT_EQ(3, report.value_metrics().data_size()); + // {{}, kStateUnknown} auto data = report.value_metrics().data(0); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, ON GROUP} data = report.value_metrics().data(1); ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_group_id()); EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, OFF GROUP} data = report.value_metrics().data(2); ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_group_id()); EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); } /* @@ -4386,6 +4482,35 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { auto fieldsInState = stateLink->mutable_fields_in_state(); *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + /* + NOTE: "1" denotes uid 1 and "2" denotes uid 2. + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------------|---------------------------------|-- + + (kStateUnknown) + 1 + |-------------| + 20 + + 2 + |----------------------------| + 40 + + (FOREGROUND) + 1 1 + |----------------------------|-------------| |------| + 40 20 10 + + + (BACKGROUND) + 1 + |------------| + 20 + 2 + |-------------|---------------------------------| + 20 50 + */ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) // ValueMetricProducer initialized. @@ -4400,64 +4525,64 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Uid 1 process state change from kStateUnknown -> Foreground .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 1 /*uid*/, 6)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 6)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 2 /*uid*/, 8)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 8)); return true; })) // Uid 2 process state change from kStateUnknown -> Background .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 2 /*uid*/, 9)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 2 /*uid*/, 9)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 1 /*uid*/, 12)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 12)); return true; })) // Uid 1 process state change from Foreground -> Background .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20); + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 1 /*uid*/, 13)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 13)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 2 /*uid*/, 11)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 11)); return true; })) // Uid 1 process state change from Background -> Foreground .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40); + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 1 /*uid*/, 17)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 17)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 2 /*uid */, 15)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, + 2 /*uid */, 15)); return true; })) // Dump report pull. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50); + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 2 /*uid*/, 20)); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 1 /*uid*/, 21)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, + 2 /*uid*/, 20)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, + 1 /*uid*/, 21)); return true; })); @@ -4489,6 +4614,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Base for dimension key {uid 2} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4505,12 +4631,14 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after uid 1 process state change kStateUnknown -> Foreground. - auto uidProcessEvent = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + auto uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {uid 1}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4528,8 +4656,18 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 1, FOREGROUND}. + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - // Base for dimension key {uid 2} + // Base for dimension key {uid 2}. it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); @@ -4538,22 +4676,42 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, kStateUnknown} + // Value for key {uid 2, kStateUnknown}. ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after uid 2 process state change kStateUnknown -> Background. - uidProcessEvent = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 40, 2 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {uid 1}. + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 2}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + + // Base for dimension key {uid 1}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); EXPECT_TRUE(itBase->second.hasCurrentState); ASSERT_EQ(1, itBase->second.currentState.getValues().size()); @@ -4563,26 +4721,33 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); - // Base for dimension key {uid 2} + // Value for key {uid 1, FOREGROUND}. it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 2, kStateUnknown} + it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 40 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); // Pull at end of first bucket. vector<shared_ptr<LogEvent>> allData; @@ -4612,6 +4777,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Base for dimension key {uid 1} it++; @@ -4629,6 +4796,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* kStateTracker::kUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Value for key {uid 1, FOREGROUND} it++; @@ -4638,6 +4807,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Value for key {uid 2, kStateUnknown} it++; @@ -4647,13 +4818,16 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* kStateTracker::kUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Bucket status after uid 1 process state change from Foreground -> Background. - uidProcessEvent = CreateUidProcessStateChangedEvent( - bucket2StartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); // Base for dimension key {uid 2}. @@ -4672,6 +4846,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + // Base for dimension key {uid 1} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4688,6 +4864,17 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 1, BACKGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 1, FOREGROUND} it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4697,6 +4884,9 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucket2StartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 2, kStateUnknown} it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4705,10 +4895,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); // Bucket status after uid 1 process state change Background->Foreground. - uidProcessEvent = CreateUidProcessStateChangedEvent( - bucket2StartTimeNs + 40, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); @@ -4729,6 +4921,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); // Base for dimension key {uid 1} it++; @@ -4746,6 +4939,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); // Value for key {uid 1, BACKGROUND} it++; @@ -4756,6 +4950,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucket2StartTimeNs + 40 * NS_PER_SEC); // Value for key {uid 1, FOREGROUND} it++; @@ -4766,6 +4962,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucket2StartTimeNs + 40 * NS_PER_SEC); // Value for key {uid 2, kStateUnknown} it++; @@ -4774,17 +4972,20 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); // Start dump report and check output. ProtoOutputStream output; std::set<string> strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 50, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); EXPECT_TRUE(report.has_value_metrics()); ASSERT_EQ(5, report.value_metrics().data_size()); + // {uid 1, BACKGROUND} auto data = report.value_metrics().data(0); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); @@ -4792,14 +4993,18 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {uid 2, kStateUnknown} data = report.value_metrics().data(1); ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {uid 1, FOREGROUND} data = report.value_metrics().data(2); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); @@ -4808,14 +5013,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size()); EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + // {uid 1, kStateUnknown} data = report.value_metrics().data(3); ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size()); EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long()); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {uid 2, BACKGROUND} data = report.value_metrics().data(4); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); @@ -4824,6 +5034,1630 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size()); EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long()); EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state when data is not + * present in pulled data during a state change. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + x (kStateUnknown) + |-----------| + 10 + + x x (ON) + |---------------------| |-----------| + 20 10 + + - (OFF) + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); + return true; + })) + // Battery saver mode state changed to OFF but data for dimension key {} is not present + // in the pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 7)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after battery saver mode ON event. + unique_ptr<LogEvent> batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + // Base for dimension key {} + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode OFF event which is not present + // in the pulled data. + unique_ptr<LogEvent> batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_FALSE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode ON event. + batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 40 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test for metric that slices by state when data is not present in pulled data + * during an event and then a flush occurs for the current bucket. With the new + * condition timer behavior, a "new" MetricDimensionKey is inserted into + * `mCurrentSlicedBucket` before intervals are closed/added to that new + * MetricDimensionKey. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + - (kStateUnknown) + + - (ON) + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized but data for dimension key {} is not present + // in the pulled data.. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + return true; + })) + // Battery saver mode state changed to ON but data for dimension key {} is not present + // in the pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after battery saver mode ON event which is not present + // in the pulled data. + unique_ptr<LogEvent> batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + /* + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------|---------------------------|-- + x (kStateUnknown) + |-----| + 10 + x x (ON) + |-----| |-----------| + 10 20 + x (OFF) + |------------------------| + 40 + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 7)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 10)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after battery saver mode ON event. + unique_ptr<LogEvent> batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode OFF event. + unique_ptr<LogEvent> batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 20 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode ON event. + batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucket2StartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, + bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{}, OFF} + data = report.value_metrics().data(2); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state when data is not + * present in pulled data during a condition change. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + + T F T (Condition) + x (ON) + |----------------------| - + 20 + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 3)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to true but data for dimension key {} is not present in the + // pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 20)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kTrue); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + unique_ptr<LogEvent> batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after condition change to false. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 30 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 40 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_FALSE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + + // {{}, ON} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state with a primary field, + * condition, and has multiple dimensions. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithConditionAndState("UID_PROCESS_STATE"); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.mutable_dimensions_in_what()->add_child()->set_field(3); + + MetricStateLink* stateLink = metric.add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + /* + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------------|---------------------------------|-- + + T F T (Condition) + (FOREGROUND) + x {1, 14} + |------| + 10 + + x {1, 16} + |------| + 10 + x {2, 8} + |-------------| + 20 + + (BACKGROUND) + x {1, 14} + |-------------| |----------|---------------------------------| + 20 15 50 + + x {1, 16} + |-------------| |----------|---------------------------------| + 20 15 50 + + x {2, 8} + |----------| |----------|-------------------| + 15 15 30 + */ + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Uid 1 process state change from kStateUnknown -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 1 /*uid*/, 3, 14 /*tag*/)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 1 /*uid*/, 3, 16 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 2 /*uid*/, 5, 8 /*tag*/)); + return true; + })) + // Uid 1 process state change from Foreground -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 5, 14 /*tag*/)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 5, 16 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 7, 8 /*tag*/)); + + return true; + })) + // Uid 2 process state change from kStateUnknown -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 25 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 2 /*uid*/, 9, 8 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 1 /*uid*/, 9, 14 /* tag */)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 1 /*uid*/, 9, 16 /* tag */)); + + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 11, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 11, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 2 /*uid*/, 11, 8 /*tag*/)); + + return true; + })) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 45 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 1 /*uid*/, 13, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 1 /*uid*/, 13, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 2 /*uid*/, 13, 8 /*tag*/)); + return true; + })) + // Uid 2 process state change from Background -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /*uid*/, 18, 8 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 14 /* tag */)); + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 16 /* tag */)); + + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 2 /*uid*/, 21, 8 /*tag*/)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}, ConditionState::kTrue); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Condition is true. + // Bucket status after uid 1 process state change kStateUnknown -> Foreground. + auto uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 1, tag 16}. + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after uid 1 process state change Foreground -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(6UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 1, tag 16}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after uid 2 process state change kStateUnknown -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 25 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 1 status after condition change to false. + // All condition timers should be turned off. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 15 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 1 status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 45 * NS_PER_SEC); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 15 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Pull at end of first bucket. + vector<shared_ptr<LogEvent>> allData; + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 14 /* tag */)); + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 16 /* tag */)); + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 13, 8 /*tag*/)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Buckets flushed after end of first bucket. + // All condition timers' behavior should rollover to bucket 2. + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(5UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(30 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 2 status after uid 2 process state change Background->Foreground. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(9UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, BACKGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, + bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(6, report.value_metrics().data_size()); + + // {{uid 1, tag 14}, FOREGROUND}. + auto data = report.value_metrics().data(0); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 1, tag 16}, BACKGROUND}. + data = report.value_metrics().data(1); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{uid 1, tag 14}, BACKGROUND}. + data = report.value_metrics().data(2); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{uid 1, tag 16}, FOREGROUND}. + data = report.value_metrics().data(3); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 2, tag 8}, FOREGROUND}. + data = report.value_metrics().data(4); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 2, tag 8}, BACKGROUND}. + data = report.value_metrics().data(5); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); } TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { @@ -4894,15 +6728,23 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::ON, itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, -1} - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Value for key {{}, ON} + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); std::unordered_map<MetricDimensionKey, ValueMetricProducer::CurrentValueBucket>::iterator it = valueProducer->mCurrentSlicedBucket.begin(); EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /*StateTracker::kUnknown*/, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); // Bucket status after battery saver mode OFF event. unique_ptr<LogEvent> batterySaverOffEvent = @@ -4917,15 +6759,27 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::OFF, itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); it = valueProducer->mCurrentSlicedBucket.begin(); EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); // Pull at end of first bucket. vector<shared_ptr<LogEvent>> allData; @@ -4944,6 +6798,15 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::OFF, itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + it = valueProducer->mCurrentSlicedBucket.begin(); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + // Value for key {{}, ON} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); // Bucket 2 status after condition change to false. valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); @@ -4964,6 +6827,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucket2StartTimeNs + 10 * NS_PER_SEC); + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); // Start dump report and check output. ProtoOutputStream output; @@ -4982,6 +6858,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); data = report.value_metrics().data(1); EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); @@ -4990,6 +6867,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(2, data.bucket_info_size()); EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); } /* diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp index 076000fab80f..65e5875e39bf 100644 --- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -920,14 +920,13 @@ TEST_F(ConfigUpdateTest, TestEventMetricPreserve) { // Create an initial config. EXPECT_TRUE(initConfig(config)); - set<int64_t> replacedMatchers; - set<int64_t> replacedConditions; unordered_map<int64_t, int> metricToActivationMap; - UpdateStatus status = UPDATE_UNKNOWN; - EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, status)); - EXPECT_EQ(status, UPDATE_PRESERVE); + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); } TEST_F(ConfigUpdateTest, TestEventMetricActivationAdded) { @@ -957,14 +956,13 @@ TEST_F(ConfigUpdateTest, TestEventMetricActivationAdded) { eventActivation->set_atom_matcher_id(startMatcher.id()); eventActivation->set_ttl_seconds(5); - set<int64_t> replacedMatchers; - set<int64_t> replacedConditions; unordered_map<int64_t, int> metricToActivationMap = {{12345, 0}}; - UpdateStatus status = UPDATE_UNKNOWN; - EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, status)); - EXPECT_EQ(status, UPDATE_REPLACE); + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); } TEST_F(ConfigUpdateTest, TestEventMetricWhatChanged) { @@ -987,14 +985,13 @@ TEST_F(ConfigUpdateTest, TestEventMetricWhatChanged) { // Create an initial config. EXPECT_TRUE(initConfig(config)); - set<int64_t> replacedMatchers = {whatMatcher.id()}; - set<int64_t> replacedConditions; unordered_map<int64_t, int> metricToActivationMap; - UpdateStatus status = UPDATE_UNKNOWN; - EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, status)); - EXPECT_EQ(status, UPDATE_REPLACE); + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); } TEST_F(ConfigUpdateTest, TestEventMetricConditionChanged) { @@ -1017,14 +1014,13 @@ TEST_F(ConfigUpdateTest, TestEventMetricConditionChanged) { // Create an initial config. EXPECT_TRUE(initConfig(config)); - set<int64_t> replacedMatchers; - set<int64_t> replacedConditions = {predicate.id()}; unordered_map<int64_t, int> metricToActivationMap; - UpdateStatus status = UPDATE_UNKNOWN; - EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, status)); - EXPECT_EQ(status, UPDATE_REPLACE); + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); } TEST_F(ConfigUpdateTest, TestMetricConditionLinkDepsChanged) { @@ -1054,14 +1050,13 @@ TEST_F(ConfigUpdateTest, TestMetricConditionLinkDepsChanged) { // Create an initial config. EXPECT_TRUE(initConfig(config)); - set<int64_t> replacedMatchers; - set<int64_t> replacedConditions = {linkPredicate.id()}; unordered_map<int64_t, int> metricToActivationMap; - UpdateStatus status = UPDATE_UNKNOWN; - EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, status)); - EXPECT_EQ(status, UPDATE_REPLACE); + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{linkPredicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); } TEST_F(ConfigUpdateTest, TestEventMetricActivationDepsChange) { @@ -1090,14 +1085,13 @@ TEST_F(ConfigUpdateTest, TestEventMetricActivationDepsChange) { // Create an initial config. EXPECT_TRUE(initConfig(config)); - set<int64_t> replacedMatchers = {startMatcher.id()}; // The activation matcher is replaced. - set<int64_t> replacedConditions; unordered_map<int64_t, int> metricToActivationMap = {{12345, 0}}; - UpdateStatus status = UPDATE_UNKNOWN; - EXPECT_TRUE(determineEventMetricUpdateStatus(config, *metric, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, status)); - EXPECT_EQ(status, UPDATE_REPLACE); + vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {startMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); } TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index a2d0b892aa0a..1f8cf8ac6d1d 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -46,39 +46,20 @@ public abstract class ActivityManagerInternal { // Access modes for handleIncomingUser. - /** - * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. - */ public static final int ALLOW_NON_FULL = 0; /** * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} - * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in the same profile - * group. + * if in the same profile group. * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. */ - public static final int ALLOW_NON_FULL_IN_PROFILE_OR_FULL = 1; - /** - * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} - * only. - */ + public static final int ALLOW_NON_FULL_IN_PROFILE = 1; public static final int ALLOW_FULL_ONLY = 2; /** * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} - * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in the same profile group. + * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group. * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. */ - public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL = 3; - /** - * Requires {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}, - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}, or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in same profile group, - * otherwise {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. (so this is an extension - * to {@link #ALLOW_NON_FULL}) - */ - public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL = 4; + public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; /** * Verify that calling app has access to the given provider. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 04f72f6dc71d..167b5a8029c0 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -6741,14 +6741,10 @@ public class AppOpsManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(int code, int uid, @Mode int mode) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { mService.setUidMode(code, uid, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -6766,7 +6762,11 @@ public class AppOpsManager { @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) { - setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); + try { + mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @hide */ @@ -6795,14 +6795,10 @@ public class AppOpsManager { @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setMode(int code, int uid, String packageName, @Mode int mode) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { mService.setMode(code, uid, packageName, mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -6822,7 +6818,11 @@ public class AppOpsManager { @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) public void setMode(@NonNull String op, int uid, @Nullable String packageName, @Mode int mode) { - setMode(strOpToOp(op), uid, packageName, mode); + try { + mService.setMode(strOpToOp(op), uid, packageName, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -7298,14 +7298,10 @@ public class AppOpsManager { * @hide */ public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { return mService.checkOperationRaw(op, uid, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -7477,20 +7473,8 @@ public class AppOpsManager { } } - int mode; - // Making the binder call "noteOperation" usually sets Binder.callingUid to the calling - // processes UID. Hence clearing the calling UID is superfluous. - // If the call is inside the system server though "noteOperation" is not a binder all, - // it is only a method call. Hence Binder.callingUid might still be set to the app that - // called the system server. This can lead to problems as not every app can see the - // same appops the system server can see. - long token = Binder.clearCallingIdentity(); - try { - mode = mService.noteOperation(op, uid, packageName, attributionTag, - collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); - } finally { - Binder.restoreCallingIdentity(token); - } + int mode = mService.noteOperation(op, uid, packageName, attributionTag, + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -7653,17 +7637,10 @@ public class AppOpsManager { } } - int mode; - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); - try { - mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName, - proxiedAttributionTag, myUid, mContext.getOpPackageName(), - mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message, - shouldCollectMessage); - } finally { - Binder.restoreCallingIdentity(token); - } + int mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName, + proxiedAttributionTag, myUid, mContext.getOpPackageName(), + mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -7713,8 +7690,6 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int checkOp(int op, int uid, String packageName) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { int mode = mService.checkOperation(op, uid, packageName); if (mode == MODE_ERRORED) { @@ -7723,8 +7698,6 @@ public class AppOpsManager { return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -7735,15 +7708,11 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int checkOpNoThrow(int op, int uid, String packageName) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { int mode = mService.checkOperation(op, uid, packageName); return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -7995,16 +7964,9 @@ public class AppOpsManager { } } - int mode; - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); - try { - mode = mService.startOperation(getClientId(), op, uid, packageName, - attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, - message, shouldCollectMessage); - } finally { - Binder.restoreCallingIdentity(token); - } + int mode = mService.startOperation(getClientId(), op, uid, packageName, + attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -8067,14 +8029,10 @@ public class AppOpsManager { */ public void finishOp(int op, int uid, @NonNull String packageName, @Nullable String attributionTag) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { mService.finishOperation(getClientId(), op, uid, packageName, attributionTag); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -8666,14 +8624,10 @@ public class AppOpsManager { // TODO: Uncomment below annotation once b/73559440 is fixed // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) public boolean isOperationActive(int code, int uid, String packageName) { - // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow - long token = Binder.clearCallingIdentity(); try { return mService.isOperationActive(code, uid, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index cd352e141994..fbc87ce0cace 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -19,12 +19,16 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -35,6 +39,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; import android.util.ArraySet; +import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.os.IResultReceiver; @@ -102,11 +107,20 @@ import java.lang.annotation.RetentionPolicy; * FLAG_ONE_SHOT, <b>both</b> FLAG_ONE_SHOT and FLAG_NO_CREATE need to be supplied. */ public final class PendingIntent implements Parcelable { + private static final String TAG = "PendingIntent"; private final IIntentSender mTarget; private IResultReceiver mCancelReceiver; private IBinder mWhitelistToken; private ArraySet<CancelListener> mCancelListeners; + /** + * It is now required to specify either {@link #FLAG_IMMUTABLE} + * or {@link #FLAG_MUTABLE} when creating a PendingIntent. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.R) + static final long PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED = 160794467L; + /** @hide */ @IntDef(flag = true, value = { @@ -115,6 +129,7 @@ public final class PendingIntent implements Parcelable { FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT, FLAG_IMMUTABLE, + FLAG_MUTABLE, Intent.FILL_IN_ACTION, Intent.FILL_IN_DATA, @@ -175,6 +190,20 @@ public final class PendingIntent implements Parcelable { public static final int FLAG_IMMUTABLE = 1<<26; /** + * Flag indicating that the created PendingIntent should be mutable. + * This flag cannot be combined with {@link #FLAG_IMMUTABLE}. <p>Up until + * {@link android.os.Build.VERSION_CODES#R}, PendingIntents are assumed to + * be mutable by default, unless {@link #FLAG_IMMUTABLE} is set. Starting + * with {@link android.os.Build.VERSION_CODES#S}, it will be required to + * explicitly specify the mutability of PendingIntents on creation with + * either (@link #FLAG_IMMUTABLE} or {@link #FLAG_MUTABLE}. It is strongly + * recommended to use {@link #FLAG_IMMUTABLE} when creating a + * PendingIntent. {@link #FLAG_MUTABLE} should only be used when some + * functionality relies on modifying the underlying intent. + */ + public static final int FLAG_MUTABLE = 1<<25; + + /** * Exception thrown when trying to send through a PendingIntent that * has been canceled or is otherwise no longer able to execute the request. */ @@ -286,6 +315,23 @@ public final class PendingIntent implements Parcelable { sOnMarshaledListener.set(listener); } + private static void checkFlags(int flags, String packageName) { + final boolean flagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0; + final boolean flagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0; + + if (flagImmutableSet && flagMutableSet) { + throw new IllegalArgumentException( + "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent"); + } + + if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED) + && !flagImmutableSet && !flagMutableSet) { + Log.wtf(TAG, packageName + ": Targeting S+ (version " + Build.VERSION_CODES.S + + " and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE" + + " be specified when creating a PendingIntent"); + } + } + /** * Retrieve a PendingIntent that will start a new activity, like calling * {@link Context#startActivity(Intent) Context.startActivity(Intent)}. @@ -350,6 +396,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); @@ -376,6 +423,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); @@ -495,6 +543,7 @@ public final class PendingIntent implements Parcelable { intents[i].prepareToLeaveProcess(context); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); } + checkFlags(flags, packageName); try { IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( @@ -521,6 +570,7 @@ public final class PendingIntent implements Parcelable { intents[i].prepareToLeaveProcess(context); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); } + checkFlags(flags, packageName); try { IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( @@ -572,6 +622,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); IIntentSender target = @@ -651,6 +702,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; + checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); IIntentSender target = diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java index e07bc0215a6b..a52fc891790c 100644 --- a/core/java/android/bluetooth/BluetoothCodecConfig.java +++ b/core/java/android/bluetooth/BluetoothCodecConfig.java @@ -51,19 +51,25 @@ public final class BluetoothCodecConfig implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface SourceCodecType {} + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_SBC = 0; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_AAC = 1; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_APTX = 2; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_LDAC = 4; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_MAX = 5; - + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; /** @hide */ @@ -75,10 +81,13 @@ public final class BluetoothCodecConfig implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface CodecPriority {} + @UnsupportedAppUsage public static final int CODEC_PRIORITY_DISABLED = -1; + @UnsupportedAppUsage public static final int CODEC_PRIORITY_DEFAULT = 0; + @UnsupportedAppUsage public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000; @@ -95,18 +104,25 @@ public final class BluetoothCodecConfig implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface SampleRate {} + @UnsupportedAppUsage public static final int SAMPLE_RATE_NONE = 0; + @UnsupportedAppUsage public static final int SAMPLE_RATE_44100 = 0x1 << 0; + @UnsupportedAppUsage public static final int SAMPLE_RATE_48000 = 0x1 << 1; + @UnsupportedAppUsage public static final int SAMPLE_RATE_88200 = 0x1 << 2; + @UnsupportedAppUsage public static final int SAMPLE_RATE_96000 = 0x1 << 3; + @UnsupportedAppUsage public static final int SAMPLE_RATE_176400 = 0x1 << 4; + @UnsupportedAppUsage public static final int SAMPLE_RATE_192000 = 0x1 << 5; @@ -120,12 +136,16 @@ public final class BluetoothCodecConfig implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface BitsPerSample {} + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_NONE = 0; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_16 = 0x1 << 0; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_24 = 0x1 << 1; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_32 = 0x1 << 2; @@ -138,10 +158,13 @@ public final class BluetoothCodecConfig implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ChannelMode {} + @UnsupportedAppUsage public static final int CHANNEL_MODE_NONE = 0; + @UnsupportedAppUsage public static final int CHANNEL_MODE_MONO = 0x1 << 0; + @UnsupportedAppUsage public static final int CHANNEL_MODE_STEREO = 0x1 << 1; private final @SourceCodecType int mCodecType; @@ -154,6 +177,7 @@ public final class BluetoothCodecConfig implements Parcelable { private final long mCodecSpecific3; private final long mCodecSpecific4; + @UnsupportedAppUsage public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority, @SampleRate int sampleRate, @BitsPerSample int bitsPerSample, @ChannelMode int channelMode, long codecSpecific1, @@ -170,6 +194,7 @@ public final class BluetoothCodecConfig implements Parcelable { mCodecSpecific4 = codecSpecific4; } + @UnsupportedAppUsage public BluetoothCodecConfig(@SourceCodecType int codecType) { mCodecType = codecType; mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; @@ -391,6 +416,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec type */ + @UnsupportedAppUsage public @SourceCodecType int getCodecType() { return mCodecType; } @@ -411,6 +437,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec priority */ + @UnsupportedAppUsage public @CodecPriority int getCodecPriority() { return mCodecPriority; } @@ -441,6 +468,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec sample rate */ + @UnsupportedAppUsage public @SampleRate int getSampleRate() { return mSampleRate; } @@ -455,6 +483,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec bits per sample */ + @UnsupportedAppUsage public @BitsPerSample int getBitsPerSample() { return mBitsPerSample; } @@ -479,6 +508,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return a codec specific value1. */ + @UnsupportedAppUsage public long getCodecSpecific1() { return mCodecSpecific1; } diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java index 1e394b830d51..7b567b4098e7 100644 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ b/core/java/android/bluetooth/BluetoothCodecStatus.java @@ -17,6 +17,7 @@ package android.bluetooth; import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -38,6 +39,7 @@ public final class BluetoothCodecStatus implements Parcelable { * This extra represents the current codec status of the A2DP * profile. */ + @UnsupportedAppUsage public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS"; @@ -196,6 +198,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return the current codec configuration */ + @UnsupportedAppUsage public @Nullable BluetoothCodecConfig getCodecConfig() { return mCodecConfig; } @@ -205,6 +208,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return an array with the codecs local capabilities */ + @UnsupportedAppUsage public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() { return mCodecsLocalCapabilities; } @@ -214,6 +218,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return an array with the codecs selectable capabilities */ + @UnsupportedAppUsage public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() { return mCodecsSelectableCapabilities; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5452f92c929f..005648ffec36 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -351,6 +351,13 @@ public abstract class Context { * due to its foreground state such as an activity or foreground service, then this flag will * allow the bound app to get the same capabilities, as long as it has the required permissions * as well. + * + * If binding from a top app and its target SDK version is at or above + * {@link android.os.Build.VERSION_CODES#R}, the app needs to + * explicitly use BIND_INCLUDE_CAPABILITIES flag to pass all capabilities to the service so the + * other app can have while-use-use access such as location, camera, microphone from background. + * If binding from a top app and its target SDK version is below + * {@link android.os.Build.VERSION_CODES#R}, BIND_INCLUDE_CAPABILITIES is implicit. */ public static final int BIND_INCLUDE_CAPABILITIES = 0x000001000; @@ -3179,8 +3186,8 @@ public abstract class Context { * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND}, * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}. - * {@link #BIND_IMPORTANT}, or - * {@link #BIND_ADJUST_WITH_ACTIVITY}. + * {@link #BIND_IMPORTANT}, {@link #BIND_ADJUST_WITH_ACTIVITY}, + * {@link #BIND_NOT_PERCEPTIBLE}, or {@link #BIND_INCLUDE_CAPABILITIES}. * @return {@code true} if the system is in the process of bringing up a * service that your client has permission to bind to; {@code false} * if the system couldn't find the service or if your client doesn't @@ -3201,6 +3208,8 @@ public abstract class Context { * @see #BIND_WAIVE_PRIORITY * @see #BIND_IMPORTANT * @see #BIND_ADJUST_WITH_ACTIVITY + * @see #BIND_NOT_PERCEPTIBLE + * @see #BIND_INCLUDE_CAPABILITIES */ public abstract boolean bindService(@RequiresPermission Intent service, @NonNull ServiceConnection conn, @BindServiceFlags int flags); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index da56abf73516..a9f143987cd4 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1878,6 +1878,7 @@ public class PackageInstaller { /** {@hide} */ @SystemApi + @TestApi public void setInstallAsInstantApp(boolean isInstantApp) { if (isInstantApp) { installFlags |= PackageManager.INSTALL_INSTANT_APP; diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index c4d123ca4382..06b5b6745bd1 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -32,10 +32,12 @@ import android.media.soundtrigger_middleware.RecognitionMode; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; +import android.os.ParcelFileDescriptor; import android.os.SharedMemory; import android.system.ErrnoException; import java.io.FileDescriptor; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.UUID; @@ -109,7 +111,12 @@ class ConversionUtil { aidlModel.type = apiModel.getType(); aidlModel.uuid = api2aidlUuid(apiModel.getUuid()); aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid()); - aidlModel.data = byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel"); + try { + aidlModel.data = ParcelFileDescriptor.dup( + byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel")); + } catch (IOException e) { + throw new RuntimeException(e); + } aidlModel.dataSize = apiModel.getData().length; return aidlModel; } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index b15109e67086..d80a7e794220 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -34,7 +34,6 @@ import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.permission.SplitPermissionInfoParcelable; -import android.os.Binder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -544,15 +543,10 @@ public final class PermissionManager { + permission); return PackageManager.PERMISSION_DENIED; } - // Clear Binder.callingUid in case this is called inside the system server. See - // more extensive comment in checkPackageNamePermissionUncached - long token = Binder.clearCallingIdentity(); try { return am.checkPermission(permission, pid, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } @@ -685,20 +679,11 @@ public final class PermissionManager { /* @hide */ private static int checkPackageNamePermissionUncached( String permName, String pkgName, @UserIdInt int userId) { - // Makeing the binder call "checkPermission" usually sets Binder.callingUid to the calling - // processes UID. Hence clearing the calling UID is superflous. - // If the call is inside the system server though "checkPermission" is not a binder all, it - // is only a method call. Hence Binder.callingUid might still be set to the app that called - // the system server. This can lead to problems as not every app can check the same - // permissions the system server can check. - long token = Binder.clearCallingIdentity(); try { return ActivityThread.getPermissionManager().checkPermission( permName, pkgName, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d3aea6aaec5d..a7055ec0f050 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7954,6 +7954,12 @@ public final class Settings { public static final String UI_NIGHT_MODE_OVERRIDE_ON = "ui_night_mode_override_on"; /** + * The last computed night mode bool the last time the phone was on + * @hide + */ + public static final String UI_NIGHT_MODE_LAST_COMPUTED = "ui_night_mode_last_computed"; + + /** * The current night mode that has been overridden to turn off by the system. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index 5a74ec0e52c0..b77265b0ebf6 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -144,6 +144,17 @@ public class IntArray implements Cloneable { } /** + * Adds the values in the specified array to this array. + */ + public void addAll(int[] values) { + final int count = values.length; + ensureCapacity(count); + + System.arraycopy(values, 0, mValues, mSize, count); + mSize += count; + } + + /** * Ensures capacity to append at least <code>count</code> values. */ private void ensureCapacity(int count) { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index d55c25fc1a4f..c698b926e233 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -316,11 +316,19 @@ public final class SurfaceControl implements Parcelable { public static final int HIDDEN = 0x00000004; /** - * Surface creation flag: The surface contains secure content, special - * measures will be taken to disallow the surface's content to be copied - * from another process. In particular, screenshots and VNC servers will - * be disabled, but other measures can take place, for instance the - * surface might not be hardware accelerated. + * Surface creation flag: Skip this layer and its children when taking a screenshot. This + * also includes mirroring and screen recording, so the layers with flag SKIP_SCREENSHOT + * will not be included on non primary displays. + * @hide + */ + public static final int SKIP_SCREENSHOT = 0x00000040; + + /** + * Surface creation flag: Special measures will be taken to disallow the surface's content to + * be copied. In particular, screenshots and secondary, non-secure displays will render black + * content instead of the surface content. + * + * @see #createDisplay(String, boolean) * @hide */ public static final int SECURE = 0x00000080; @@ -482,15 +490,6 @@ public final class SurfaceControl implements Parcelable { public static final int POWER_MODE_ON_SUSPEND = 4; /** - * A value for windowType used to indicate that the window should be omitted from screenshots - * and display mirroring. A temporary workaround until we express such things with - * the hierarchy. - * TODO: b/64227542 - * @hide - */ - public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731; - - /** * internal representation of how to interpret pixel value, used only to convert to ColorSpace. */ private static final int INTERNAL_DATASPACE_SRGB = 142671872; @@ -3287,6 +3286,22 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * Adds or removes the flag SKIP_SCREENSHOT of the surface. Setting the flag is equivalent + * to creating the Surface with the {@link #SKIP_SCREENSHOT} flag. + * + * @hide + */ + public Transaction setSkipScreenshot(SurfaceControl sc, boolean skipScrenshot) { + checkPreconditions(sc); + if (skipScrenshot) { + nativeSetFlags(mNativeObject, sc.mNativeObject, SKIP_SCREENSHOT, SKIP_SCREENSHOT); + } else { + nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SKIP_SCREENSHOT); + } + return this; + } + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index e58990eff2c8..7516a879211f 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6613,22 +6613,29 @@ public class BatteryStatsImpl extends BatteryStats { * the power consumption to the calling app. */ public void noteBinderCallStats(int workSourceUid, long incrementalCallCount, - Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) { - noteBinderCallStats(workSourceUid, incrementalCallCount, callStats, binderThreadNativeTids, + Collection<BinderCallsStats.CallStat> callStats) { + noteBinderCallStats(workSourceUid, incrementalCallCount, callStats, mClocks.elapsedRealtime(), mClocks.uptimeMillis()); } public void noteBinderCallStats(int workSourceUid, long incrementalCallCount, - Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids, + Collection<BinderCallsStats.CallStat> callStats, long elapsedRealtimeMs, long uptimeMs) { synchronized (this) { getUidStatsLocked(workSourceUid, elapsedRealtimeMs, uptimeMs) .noteBinderCallStatsLocked(incrementalCallCount, callStats); - mSystemServerCpuThreadReader.setBinderThreadNativeTids(binderThreadNativeTids); } } /** + * Takes note of native IDs of threads taking incoming binder calls. The CPU time + * of these threads is attributed to the apps making those binder calls. + */ + public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) { + mSystemServerCpuThreadReader.setBinderThreadNativeTids(binderThreadNativeTids); + } + + /** * Estimates the proportion of system server CPU activity handling incoming binder calls * that can be attributed to each app */ diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index f5bef0b006f5..70b1ad49d8b8 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -119,8 +119,8 @@ public class BinderCallsStats implements BinderInternal.Observer { if (uidEntry != null) { ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats; mCallStatsObserver.noteCallStats(uidEntry.workSourceUid, - uidEntry.incrementalCallCount, callStats.values(), - mNativeTids.toArray()); + uidEntry.incrementalCallCount, callStats.values() + ); uidEntry.incrementalCallCount = 0; for (int j = callStats.size() - 1; j >= 0; j--) { callStats.valueAt(j).incrementalCallCount = 0; @@ -168,6 +168,7 @@ public class BinderCallsStats implements BinderInternal.Observer { public void setCallStatsObserver( BinderInternal.CallStatsObserver callStatsObserver) { mCallStatsObserver = callStatsObserver; + noteBinderThreadNativeIds(); noteCallsStatsDelayed(); } @@ -182,13 +183,13 @@ public class BinderCallsStats implements BinderInternal.Observer { @Override @Nullable public CallSession callStarted(Binder binder, int code, int workSourceUid) { + noteNativeThreadId(); + if (!mRecordingAllTransactionsForUid && (mDeviceState == null || mDeviceState.isCharging())) { return null; } - noteNativeThreadId(); - final CallSession s = obtainCallSession(); s.binderClass = binder.getClass(); s.transactionCode = code; @@ -359,6 +360,16 @@ public class BinderCallsStats implements BinderInternal.Observer { mNativeTids = copyOnWriteArray; } } + + noteBinderThreadNativeIds(); + } + + private void noteBinderThreadNativeIds() { + if (mCallStatsObserver == null) { + return; + } + + mCallStatsObserver.noteBinderThreadNativeIds(getNativeTids()); } /** diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java index 2645b8e84cf1..c14d8d805d29 100644 --- a/core/java/com/android/internal/os/BinderInternal.java +++ b/core/java/com/android/internal/os/BinderInternal.java @@ -143,8 +143,12 @@ public class BinderInternal { * Notes incoming binder call stats associated with this work source UID. */ void noteCallStats(int workSourceUid, long incrementalCallCount, - Collection<BinderCallsStats.CallStat> callStats, - int[] binderThreadNativeTids); + Collection<BinderCallsStats.CallStat> callStats); + + /** + * Notes the native IDs of threads taking incoming binder calls. + */ + void noteBinderThreadNativeIds(int[] binderThreadNativeTids); } /** diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java index 1cdd42c7403e..3aa2390375ec 100644 --- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java +++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java @@ -31,7 +31,7 @@ import java.util.Arrays; */ public class SystemServerCpuThreadReader { private KernelCpuThreadReader mKernelCpuThreadReader; - private int[] mBinderThreadNativeTids; + private int[] mBinderThreadNativeTids = new int[0]; // Sorted private int[] mThreadCpuTimesUs; private int[] mBinderThreadCpuTimesUs; @@ -75,7 +75,8 @@ public class SystemServerCpuThreadReader { } public void setBinderThreadNativeTids(int[] nativeTids) { - mBinderThreadNativeTids = nativeTids; + mBinderThreadNativeTids = nativeTids.clone(); + Arrays.sort(mBinderThreadNativeTids); } /** @@ -107,7 +108,8 @@ public class SystemServerCpuThreadReader { int threadCpuUsagesSize = threadCpuUsages.size(); for (int j = 0; j < threadCpuUsagesSize; j++) { KernelCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(j); - boolean isBinderThread = isBinderThread(tcu.threadId); + boolean isBinderThread = + Arrays.binarySearch(mBinderThreadNativeTids, tcu.threadId) >= 0; final int len = Math.min(tcu.usageTimesMillis.length, mThreadCpuTimesUs.length); for (int k = 0; k < len; k++) { @@ -138,14 +140,4 @@ public class SystemServerCpuThreadReader { return mDeltaCpuThreadTimes; } - private boolean isBinderThread(int threadId) { - if (mBinderThreadNativeTids != null) { - for (int i = 0; i < mBinderThreadNativeTids.length; i++) { - if (threadId == mBinderThreadNativeTids[i]) { - return true; - } - } - } - return false; - } } diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 9f7436a13bdc..9874c6aabf04 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -72,7 +72,9 @@ public enum ProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM), WM_DEBUG_WINDOW_ORGANIZER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), - TEST_GROUP(true, true, false, "WindowManagetProtoLogTest"); + WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM), + TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; private volatile boolean mLogToProto; diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 03975daf1f76..25a9bbd8b445 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4374,7 +4374,7 @@ </string> <!-- Dialog title for dialog shown when the multiple accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] --> - <string name="accessibility_shortcut_multiple_service_warning_title">Turn on accessibility features?</string> + <string name="accessibility_shortcut_multiple_service_warning_title">Turn on shortcut for accessibility features?</string> <!-- Message shown in dialog when user is in the process of enabling the multiple accessibility service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] --> <string name="accessibility_shortcut_multiple_service_warning">Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="service" example="TalkBack">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility.</string> @@ -4383,7 +4383,7 @@ <string name="accessibility_shortcut_multiple_service_list">\t• <xliff:g id="service" example="TalkBack">%1$s</xliff:g>\n</string> <!-- Dialog title for dialog shown when this accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] --> - <string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g>?</string> + <string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g> shortcut?</string> <!-- Message shown in dialog when user is in the process of enabling this accessibility service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] --> <string name="accessibility_shortcut_single_service_warning">Holding down both volume keys for a few seconds turns on <xliff:g id="service" example="TalkBack">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility.</string> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 6ae6faa5446e..71cb2acde94e 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -228,15 +228,6 @@ </intent-filter> </activity> - <activity android:name="android.widget.focus.ListOfButtons" - android:label="ListOfButtons" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> - </intent-filter> - </activity> - <activity android:name="android.widget.focus.LinearLayoutGrid" android:label="LinearLayoutGrid" android:exported="true"> diff --git a/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java b/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java deleted file mode 100644 index 936c9999e7f8..000000000000 --- a/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.widget.focus; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ListView; - -import com.google.android.collect.Lists; - -import java.util.List; - -public class ListOfEditTexts extends Activity { - - private int mLinesPerEditText = 12; - - private ListView mListView; - private LinearLayout mLinearLayout; - - public ListView getListView() { - return mListView; - } - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - // create linear layout - mLinearLayout = new LinearLayout(this); - mLinearLayout.setOrientation(LinearLayout.VERTICAL); - mLinearLayout.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - // add a button above - Button buttonAbove = new Button(this); - buttonAbove.setLayoutParams( - new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - buttonAbove.setText("button above list"); - mLinearLayout.addView(buttonAbove); - - // add a list view to it - mListView = new ListView(this); - mListView.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - mListView.setDrawSelectorOnTop(false); - mListView.setItemsCanFocus(true); - mListView.setLayoutParams((new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - 0, - 1f))); - - List<String> bodies = Lists.newArrayList( - getBody("zero hello, my name is android"), - getBody("one i'm a paranoid android"), - getBody("two i robot. huh huh."), - getBody("three not the g-phone!")); - - mListView.setAdapter(new MyAdapter(this, bodies)); - mLinearLayout.addView(mListView); - - // add button below - Button buttonBelow = new Button(this); - buttonBelow.setLayoutParams( - new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - buttonBelow.setText("button below list"); - mLinearLayout.addView(buttonBelow); - - setContentView(mLinearLayout); - } - - String getBody(String line) { - StringBuilder sb = new StringBuilder((line.length() + 5) * mLinesPerEditText); - for (int i = 0; i < mLinesPerEditText; i++) { - sb.append(i + 1).append(' ').append(line); - if (i < mLinesPerEditText - 1) { - sb.append('\n'); // all but last line - } - } - return sb.toString(); - } - - - private static class MyAdapter extends ArrayAdapter<String> { - - public MyAdapter(Context context, List<String> bodies) { - super(context, 0, bodies); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - String body = getItem(position); - - if (convertView != null) { - ((EditText) convertView).setText(body); - return convertView; - } - - EditText editText = new EditText(getContext()); - editText.setText(body); - return editText; - } - } -} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java index 22c41f3c9622..85f9c97629e3 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java @@ -60,7 +60,7 @@ public class BatteryStatsBinderCallStatsTest extends TestCase { stat1.cpuTimeMicros = 1000; callStats.add(stat1); - bi.noteBinderCallStats(workSourceUid, 42, callStats, null); + bi.noteBinderCallStats(workSourceUid, 42, callStats); callStats.clear(); BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(callingUid, @@ -70,7 +70,7 @@ public class BatteryStatsBinderCallStatsTest extends TestCase { stat2.cpuTimeMicros = 500; callStats.add(stat2); - bi.noteBinderCallStats(workSourceUid, 8, callStats, null); + bi.noteBinderCallStats(workSourceUid, 8, callStats); BatteryStatsImpl.Uid uid = bi.getUidStatsLocked(workSourceUid); assertEquals(42 + 8, uid.getBinderCallCount()); @@ -112,7 +112,7 @@ public class BatteryStatsBinderCallStatsTest extends TestCase { stat1b.cpuTimeMicros = 1500; callStats.add(stat1b); - bi.noteBinderCallStats(workSourceUid1, 65, callStats, null); + bi.noteBinderCallStats(workSourceUid1, 65, callStats); // No recorded stats for some methods. Must use the global average. callStats.clear(); @@ -121,11 +121,11 @@ public class BatteryStatsBinderCallStatsTest extends TestCase { stat2.incrementalCallCount = 10; callStats.add(stat2); - bi.noteBinderCallStats(workSourceUid2, 40, callStats, null); + bi.noteBinderCallStats(workSourceUid2, 40, callStats); // No stats for any calls. Must use the global average callStats.clear(); - bi.noteBinderCallStats(workSourceUid3, 50, callStats, null); + bi.noteBinderCallStats(workSourceUid3, 50, callStats); bi.updateSystemServiceCallStats(); diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index 96250db4aa51..0eb34a993dec 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -47,8 +47,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.Set; @SmallTest @RunWith(AndroidJUnit4.class) @@ -770,13 +772,27 @@ public class BinderCallsStatsTest { bcs.setSamplingInterval(1); bcs.setTrackScreenInteractive(false); + final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>(); - bcs.setCallStatsObserver( - (workSourceUid, incrementalCallCount, callStats, binderThreadIds) -> - callStatsList.addAll(callStats)); + final Set<Integer> nativeTids = new HashSet<>(); + bcs.setCallStatsObserver(new BinderInternal.CallStatsObserver() { + @Override + public void noteCallStats(int workSourceUid, long incrementalCallCount, + Collection<BinderCallsStats.CallStat> callStats) { + callStatsList.addAll(callStats); + } + + @Override + public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) { + for (int tid : binderThreadNativeTids) { + nativeTids.add(tid); + } + } + }); Binder binder = new Binder(); + bcs.nativeTid = 1000; CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); @@ -785,6 +801,7 @@ public class BinderCallsStatsTest { bcs.time += 20; bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); + bcs.nativeTid = 2000; callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID); bcs.time += 30; bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); @@ -809,6 +826,10 @@ public class BinderCallsStatsTest { assertEquals(30, callStats.maxCpuTimeMicros); } } + + assertEquals(2, nativeTids.size()); + assertTrue(nativeTids.contains(1000)); + assertTrue(nativeTids.contains(2000)); } @Test diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java index ac5443e1c7ce..2eee140b921f 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -87,7 +87,7 @@ public class SystemServicePowerCalculatorTest { stat1.cpuTimeMicros = 1000000; callStats.add(stat1); - mMockBatteryStats.noteBinderCallStats(workSourceUid1, 100, callStats, null); + mMockBatteryStats.noteBinderCallStats(workSourceUid1, 100, callStats); callStats.clear(); BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(workSourceUid2, @@ -97,7 +97,7 @@ public class SystemServicePowerCalculatorTest { stat2.cpuTimeMicros = 9000000; callStats.add(stat2); - mMockBatteryStats.noteBinderCallStats(workSourceUid2, 100, callStats, null); + mMockBatteryStats.noteBinderCallStats(workSourceUid2, 100, callStats); mMockBatteryStats.updateSystemServiceCallStats(); mMockBatteryStats.updateSystemServerThreadStats(); diff --git a/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java b/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java index 55d186c292f6..469a4ccd05e4 100644 --- a/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java @@ -16,16 +16,17 @@ package com.android.internal.util; -import com.android.internal.util.CharSequences; import static com.android.internal.util.CharSequences.forAsciiBytes; -import junit.framework.TestCase; + import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + public class CharSequencesTest extends TestCase { @SmallTest public void testCharSequences() { - String s = "Crazy Bob"; + String s = "Hello Bob"; byte[] bytes = s.getBytes(); String copy = toString(forAsciiBytes(bytes)); @@ -34,11 +35,11 @@ public class CharSequencesTest extends TestCase { copy = toString(forAsciiBytes(bytes, 0, s.length())); assertTrue(s.equals(copy)); - String crazy = toString(forAsciiBytes(bytes, 0, 5)); - assertTrue("Crazy".equals(crazy)); + String hello = toString(forAsciiBytes(bytes, 0, 5)); + assertTrue("Hello".equals(hello)); - String a = toString(forAsciiBytes(bytes, 0, 3).subSequence(2, 3)); - assertTrue("a".equals(a)); + String l = toString(forAsciiBytes(bytes, 0, 3).subSequence(2, 3)); + assertTrue("l".equals(l)); String empty = toString(forAsciiBytes(bytes, 0, 3).subSequence(3, 3)); assertTrue("".equals(empty)); diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 0a0681479278..dd8f40d586bc 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -153,8 +153,8 @@ <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" /> <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" /> + <assign-permission name="android.permission.INTERNET" uid="media" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" /> <assign-permission name="android.permission.INTERNET" uid="shell" /> @@ -164,7 +164,6 @@ <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" /> @@ -175,10 +174,8 @@ <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" /> <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" /> <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="graphics" /> <assign-permission name="android.permission.DUMP" uid="incidentd" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="incidentd" /> @@ -193,10 +190,8 @@ <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" /> <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" /> <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="statsd" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="statsd" /> <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="gpu_service" /> <split-permission name="android.permission.ACCESS_FINE_LOCATION"> <new-permission name="android.permission.ACCESS_COARSE_LOCATION" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 6b80bb60ec67..86e7adf945b7 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -151,6 +151,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1910833551": { + "message": "SyncSet{%x:%d} Start for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-1895337367": { "message": "Delete root task display=%d winMode=%d", "level": "VERBOSE", @@ -463,6 +469,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1387080937": { + "message": "SyncSet{%x:%d} Child ready, now ready=%b and waiting on %d transactions", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-1364754753": { "message": "Task vanished taskId=%d", "level": "VERBOSE", @@ -481,6 +493,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1340783230": { + "message": "SyncSet{%x:%d} Added %s. now waiting on %d transactions", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-1340540100": { "message": "Creating SnapshotStartingData", "level": "VERBOSE", @@ -859,6 +877,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DragState.java" }, + "-678300709": { + "message": "SyncSet{%x:%d} Trying to add %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-668956537": { "message": " THUMBNAIL %s: CREATE", "level": "INFO", @@ -1735,6 +1759,12 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, + "590184240": { + "message": "- NOT adding to sync: visible=%b hasListener=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "594260577": { "message": "createWallpaperAnimations()", "level": "DEBUG", @@ -2005,6 +2035,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "1000601037": { + "message": "SyncSet{%x:%d} Set ready", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "1001904964": { "message": "***** BOOT TIMEOUT: forcing display enabled", "level": "WARN", @@ -2599,6 +2635,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "2001924866": { + "message": "SyncSet{%x:%d} Finished. Reporting %d containers to %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "2016061474": { "message": "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d Callers=%s", "level": "VERBOSE", @@ -2775,6 +2817,9 @@ "WM_DEBUG_SWITCH": { "tag": "WindowManager" }, + "WM_DEBUG_SYNC_ENGINE": { + "tag": "WindowManager" + }, "WM_DEBUG_WINDOW_MOVEMENT": { "tag": "WindowManager" }, diff --git a/graphics/java/android/graphics/BlurShader.java b/graphics/java/android/graphics/BlurShader.java index 779a89051060..3bc811983336 100644 --- a/graphics/java/android/graphics/BlurShader.java +++ b/graphics/java/android/graphics/BlurShader.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.NonNull; import android.annotation.Nullable; /** @@ -28,6 +29,7 @@ public final class BlurShader extends Shader { private final float mRadiusX; private final float mRadiusY; private final Shader mInputShader; + private final TileMode mEdgeTreatment; private long mNativeInputShader = 0; @@ -35,22 +37,42 @@ public final class BlurShader extends Shader { * Create a {@link BlurShader} that blurs the contents of the optional input shader * with the specified radius along the x and y axis. If no input shader is provided * then all drawing commands issued with a {@link android.graphics.Paint} that this - * shader is installed in will be blurred + * shader is installed in will be blurred. + * + * This uses a default {@link TileMode#DECAL} for edge treatment + * * @param radiusX Radius of blur along the X axis * @param radiusY Radius of blur along the Y axis * @param inputShader Input shader that provides the content to be blurred */ public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader) { + this(radiusX, radiusY, inputShader, TileMode.DECAL); + } + + /** + * Create a {@link BlurShader} that blurs the contents of the optional input shader + * with the specified radius along the x and y axis. If no input shader is provided + * then all drawing commands issued with a {@link android.graphics.Paint} that this + * shader is installed in will be blurred + * @param radiusX Radius of blur along the X axis + * @param radiusY Radius of blur along the Y axis + * @param inputShader Input shader that provides the content to be blurred + * @param edgeTreatment Policy for how to blur content near edges of the blur shader + */ + public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader, + @NonNull TileMode edgeTreatment) { mRadiusX = radiusX; mRadiusY = radiusY; mInputShader = inputShader; + mEdgeTreatment = edgeTreatment; } /** @hide **/ @Override protected long createNativeInstance(long nativeMatrix) { mNativeInputShader = mInputShader != null ? mInputShader.getNativeInstance() : 0; - return nativeCreate(nativeMatrix, mRadiusX, mRadiusY, mNativeInputShader); + return nativeCreate(nativeMatrix, mRadiusX, mRadiusY, mNativeInputShader, + mEdgeTreatment.nativeInt); } /** @hide **/ @@ -61,5 +83,5 @@ public final class BlurShader extends Shader { } private static native long nativeCreate(long nativeMatrix, float radiusX, float radiusY, - long inputShader); + long inputShader, int edgeTreatment); } diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java index f9033a53d7e6..d408ac3718ca 100644 --- a/graphics/java/android/graphics/ParcelableColorSpace.java +++ b/graphics/java/android/graphics/ParcelableColorSpace.java @@ -25,8 +25,6 @@ import android.os.Parcelable; * A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace * must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance * that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}. - * TODO: Make public - * @hide */ public final class ParcelableColorSpace extends ColorSpace implements Parcelable { private final ColorSpace mColorSpace; diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index 8154ebf1e508..d71ff1138b25 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -95,7 +95,11 @@ public class Shader { * repeat the shader's image horizontally and vertically, alternating * mirror images so that adjacent images always seam */ - MIRROR (2); + MIRROR(2), + /** + * Only draw within the original domain, return transparent-black everywhere else + */ + DECAL(3); TileMode(int nativeInt) { this.nativeInt = nativeInt; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index cfba5d4f6aa2..a690840e91a9 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -735,8 +735,7 @@ void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { // ---------------------------------------------------------------------------- void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x, - float y, float boundsLeft, float boundsTop, float boundsRight, - float boundsBottom, float totalAdvance) { + float y, float totalAdvance) { if (count <= 0 || paint.nothingToDraw()) return; Paint paintCopy(paint); if (mPaintFilter) { diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 1df2b2671659..2cb850c83934 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -161,8 +161,7 @@ protected: void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); } virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x, - float y, float boundsLeft, float boundsTop, float boundsRight, - float boundsBottom, float totalAdvance) override; + float y, float totalAdvance) override; virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const Paint& paint, const SkPath& path, size_t start, size_t end) override; diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 2a377bbb83f2..2001b5620f84 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -84,13 +84,12 @@ static void simplifyPaint(int color, Paint* paint) { class DrawTextFunctor { public: DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, - float y, minikin::MinikinRect& bounds, float totalAdvance) + float y, float totalAdvance) : layout(layout) , canvas(canvas) , paint(paint) , x(x) , y(y) - , bounds(bounds) , totalAdvance(totalAdvance) {} void operator()(size_t start, size_t end) { @@ -114,19 +113,16 @@ public: Paint outlinePaint(paint); simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); - canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop, - bounds.mRight, bounds.mBottom, totalAdvance); + canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance); // inner Paint innerPaint(paint); simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); innerPaint.setStyle(SkPaint::kFill_Style); - canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop, - bounds.mRight, bounds.mBottom, totalAdvance); + canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance); } else { // standard draw path - canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, bounds.mLeft, bounds.mTop, - bounds.mRight, bounds.mBottom, totalAdvance); + canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance); } } @@ -136,7 +132,6 @@ private: const Paint& paint; float x; float y; - minikin::MinikinRect& bounds; float totalAdvance; }; @@ -156,15 +151,12 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, x += MinikinUtils::xOffsetForTextAlign(&paint, layout); - minikin::MinikinRect bounds; - layout.getBounds(&bounds); - // Set align to left for drawing, as we don't want individual // glyphs centered or right-aligned; the offset above takes // care of all alignment. paint.setTextAlign(Paint::kLeft_Align); - DrawTextFunctor f(layout, this, paint, x, y, bounds, layout.getAdvance()); + DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance()); MinikinUtils::forFontRun(layout, &paint, f); } diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 333567b0cf91..817c7ee9077f 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -288,8 +288,7 @@ protected: * totalAdvance: used to define width of text decorations (underlines, strikethroughs). */ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x, - float y, float boundsLeft, float boundsTop, float boundsRight, - float boundsBottom, float totalAdvance) = 0; + float y,float totalAdvance) = 0; virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const Paint& paint, const SkPath& path, size_t start, size_t end) = 0; diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 7cb77233846f..0a194f9dd666 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -224,7 +224,7 @@ static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr, /////////////////////////////////////////////////////////////////////////////////////////////// static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX, - jfloat sigmaY, jlong shaderHandle) { + jfloat sigmaY, jlong shaderHandle, jint edgeTreatment) { auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); auto* inputShader = reinterpret_cast<Shader*>(shaderHandle); @@ -232,6 +232,7 @@ static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX, sigmaY, inputShader, + static_cast<SkTileMode>(edgeTreatment), matrix ); return reinterpret_cast<jlong>(blurShader); @@ -291,7 +292,7 @@ static const JNINativeMethod gBitmapShaderMethods[] = { }; static const JNINativeMethod gBlurShaderMethods[] = { - { "nativeCreate", "(JFFJ)J", (void*)BlurShader_create } + { "nativeCreate", "(JFFJI)J", (void*)BlurShader_create } }; static const JNINativeMethod gLinearGradientMethods[] = { diff --git a/libs/hwui/shader/BlurShader.cpp b/libs/hwui/shader/BlurShader.cpp index fa10be100bca..2abd8714204b 100644 --- a/libs/hwui/shader/BlurShader.cpp +++ b/libs/hwui/shader/BlurShader.cpp @@ -20,13 +20,14 @@ #include "utils/Blur.h" namespace android::uirenderer { -BlurShader::BlurShader(float radiusX, float radiusY, Shader* inputShader, const SkMatrix* matrix) +BlurShader::BlurShader(float radiusX, float radiusY, Shader* inputShader, SkTileMode edgeTreatment, + const SkMatrix* matrix) : Shader(matrix) , skImageFilter( SkImageFilters::Blur( Blur::convertRadiusToSigma(radiusX), Blur::convertRadiusToSigma(radiusY), - SkTileMode::kClamp, + edgeTreatment, inputShader ? inputShader->asSkImageFilter() : nullptr, nullptr) ) { } diff --git a/libs/hwui/shader/BlurShader.h b/libs/hwui/shader/BlurShader.h index 9eb22bd11f4a..60a15898893e 100644 --- a/libs/hwui/shader/BlurShader.h +++ b/libs/hwui/shader/BlurShader.h @@ -30,8 +30,12 @@ public: * * This will blur the contents of the provided input shader if it is non-null, otherwise * the source bitmap will be blurred instead. + * + * The edge treatment parameter determines how content near the edges of the source is to + * participate in the blur */ - BlurShader(float radiusX, float radiusY, Shader* inputShader, const SkMatrix* matrix); + BlurShader(float radiusX, float radiusY, Shader* inputShader, SkTileMode edgeTreatment, + const SkMatrix* matrix); ~BlurShader() override; protected: sk_sp<SkImageFilter> makeSkImageFilter() override; diff --git a/media/Android.bp b/media/Android.bp index 8895b3a9a2ba..828707b70e7b 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -1,85 +1,52 @@ aidl_interface { name: "audio_common-aidl", unstable: true, - local_include_dir: "java", + local_include_dir: "aidl", srcs: [ - "java/android/media/audio/common/AudioChannelMask.aidl", - "java/android/media/audio/common/AudioConfig.aidl", - "java/android/media/audio/common/AudioFormat.aidl", - "java/android/media/audio/common/AudioOffloadInfo.aidl", - "java/android/media/audio/common/AudioStreamType.aidl", - "java/android/media/audio/common/AudioUsage.aidl", + "aidl/android/media/audio/common/AudioChannelMask.aidl", + "aidl/android/media/audio/common/AudioConfig.aidl", + "aidl/android/media/audio/common/AudioFormat.aidl", + "aidl/android/media/audio/common/AudioOffloadInfo.aidl", + "aidl/android/media/audio/common/AudioStreamType.aidl", + "aidl/android/media/audio/common/AudioUsage.aidl", ], - backend: - { - cpp: { - enabled: true, - }, - java: { - // Already generated as part of the entire media java library. - enabled: false, - }, - }, } aidl_interface { name: "media_permission-aidl", unstable: true, - local_include_dir: "java", + local_include_dir: "aidl", srcs: [ - "java/android/media/permission/Identity.aidl", + "aidl/android/media/permission/Identity.aidl", ], - backend: - { - cpp: { - enabled: true, - }, - java: { - // Already generated as part of the entire media java library. - enabled: false, - }, - }, } aidl_interface { name: "soundtrigger_middleware-aidl", unstable: true, - local_include_dir: "java", + local_include_dir: "aidl", srcs: [ - "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl", - "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl", - "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl", - "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl", - "java/android/media/soundtrigger_middleware/ModelParameter.aidl", - "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl", - "java/android/media/soundtrigger_middleware/Phrase.aidl", - "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl", - "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl", - "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl", - "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl", - "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl", - "java/android/media/soundtrigger_middleware/RecognitionMode.aidl", - "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl", - "java/android/media/soundtrigger_middleware/SoundModel.aidl", - "java/android/media/soundtrigger_middleware/SoundModelType.aidl", - "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl", - "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl", - "java/android/media/soundtrigger_middleware/Status.aidl", + "aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl", + "aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl", + "aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl", + "aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl", + "aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl", + "aidl/android/media/soundtrigger_middleware/ModelParameter.aidl", + "aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl", + "aidl/android/media/soundtrigger_middleware/Phrase.aidl", + "aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl", + "aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl", + "aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl", + "aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl", + "aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl", + "aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl", + "aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl", + "aidl/android/media/soundtrigger_middleware/SoundModel.aidl", + "aidl/android/media/soundtrigger_middleware/SoundModelType.aidl", + "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl", + "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl", + "aidl/android/media/soundtrigger_middleware/Status.aidl", ], - backend: - { - cpp: { - enabled: true, - }, - java: { - // Already generated as part of the entire media java library. - enabled: false, - }, - ndk: { - // Not currently needed, and disabled because of b/146172425 - enabled: false, - }, - }, imports: [ "audio_common-aidl", "media_permission-aidl", diff --git a/media/OWNERS b/media/OWNERS index c95ac6c210c0..36df3a05e0ee 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -9,6 +9,7 @@ hkuang@google.com hunga@google.com insun@google.com jaewan@google.com +jinpark@google.com jmtrivi@google.com jsharkey@android.com klhyun@google.com @@ -17,6 +18,3 @@ marcone@google.com philburk@google.com sungsoo@google.com wonsik@google.com - -# For maintaining sync with AndroidX code -per-file ExifInterface.java = jinpark@google.com, sungsoo@google.com diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/aidl/android/media/audio/common/AudioChannelMask.aidl index b9b08e6921bc..b9b08e6921bc 100644 --- a/media/java/android/media/audio/common/AudioChannelMask.aidl +++ b/media/aidl/android/media/audio/common/AudioChannelMask.aidl diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/aidl/android/media/audio/common/AudioConfig.aidl index 50dd796e1fa0..50dd796e1fa0 100644 --- a/media/java/android/media/audio/common/AudioConfig.aidl +++ b/media/aidl/android/media/audio/common/AudioConfig.aidl diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/aidl/android/media/audio/common/AudioFormat.aidl index aadc8e26cce3..aadc8e26cce3 100644 --- a/media/java/android/media/audio/common/AudioFormat.aidl +++ b/media/aidl/android/media/audio/common/AudioFormat.aidl diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl index ec10d71135ae..ec10d71135ae 100644 --- a/media/java/android/media/audio/common/AudioOffloadInfo.aidl +++ b/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/aidl/android/media/audio/common/AudioStreamType.aidl index c54566726350..c54566726350 100644 --- a/media/java/android/media/audio/common/AudioStreamType.aidl +++ b/media/aidl/android/media/audio/common/AudioStreamType.aidl diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/aidl/android/media/audio/common/AudioUsage.aidl index ef348165b22c..ef348165b22c 100644 --- a/media/java/android/media/audio/common/AudioUsage.aidl +++ b/media/aidl/android/media/audio/common/AudioUsage.aidl diff --git a/media/java/android/media/permission/Identity.aidl b/media/aidl/android/media/permission/Identity.aidl index 361497d59ea9..361497d59ea9 100644 --- a/media/java/android/media/permission/Identity.aidl +++ b/media/aidl/android/media/permission/Identity.aidl diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl index 97a8849c7b07..97a8849c7b07 100644 --- a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl index 3dbc70556bd3..3dbc70556bd3 100644 --- a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl index 726af7681979..726af7681979 100644 --- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl index d1126b9006e0..d1126b9006e0 100644 --- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl index c4a57857dd3d..c4a57857dd3d 100644 --- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl index 09936278e93a..09936278e93a 100644 --- a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl index d6948a87dc6d..d6948a87dc6d 100644 --- a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl diff --git a/media/java/android/media/soundtrigger_middleware/OWNERS b/media/aidl/android/media/soundtrigger_middleware/OWNERS index e5d037003ac4..e5d037003ac4 100644 --- a/media/java/android/media/soundtrigger_middleware/OWNERS +++ b/media/aidl/android/media/soundtrigger_middleware/OWNERS diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/aidl/android/media/soundtrigger_middleware/Phrase.aidl index 98a489f8a6a9..98a489f8a6a9 100644 --- a/media/java/android/media/soundtrigger_middleware/Phrase.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/Phrase.aidl diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl index 6a3ec61d1ebf..6a3ec61d1ebf 100644 --- a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl index cb96bf37a95d..cb96bf37a95d 100644 --- a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl index 81028c1608ea..81028c1608ea 100644 --- a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl index 5c0eeb1e32b1..5c0eeb1e32b1 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl index a237ec1aa3b3..a237ec1aa3b3 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl index d8bfff4bec6f..d8bfff4bec6f 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl index d563edca547d..d563edca547d 100644 --- a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl index 81d8291e85aa..cee3635a1e3b 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl @@ -16,6 +16,7 @@ package android.media.soundtrigger_middleware; import android.media.soundtrigger_middleware.SoundModelType; +import android.os.ParcelFileDescriptor; /** * Base sound model descriptor. This struct can be extended for various specific types by way of @@ -32,7 +33,7 @@ parcelable SoundModel { * was build for */ String vendorUuid; /** Opaque data transparent to Android framework */ - FileDescriptor data; + ParcelFileDescriptor data; /** Size of the above data, in bytes. */ int dataSize; } diff --git a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl index f2abc9af7780..f2abc9af7780 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl index 667135ff61b9..667135ff61b9 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl index 9c56e7b98b3f..9c56e7b98b3f 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/aidl/android/media/soundtrigger_middleware/Status.aidl index c7623f5bf491..c7623f5bf491 100644 --- a/media/java/android/media/soundtrigger_middleware/Status.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/Status.aidl diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e1e55c25b3fa..22b5ca53e7e9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -96,8 +96,8 @@ public class AudioManager { private Context mOriginalContext; private Context mApplicationContext; private long mVolumeKeyUpTime; - private final boolean mUseVolumeKeySounds; - private final boolean mUseFixedVolume; + private boolean mUseFixedVolumeInitialized; + private boolean mUseFixedVolume; private static final String TAG = "AudioManager"; private static final boolean DEBUG = false; private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler(); @@ -711,8 +711,6 @@ public class AudioManager { */ @UnsupportedAppUsage public AudioManager() { - mUseVolumeKeySounds = true; - mUseFixedVolume = false; } /** @@ -721,10 +719,6 @@ public class AudioManager { @UnsupportedAppUsage public AudioManager(Context context) { setContext(context); - mUseVolumeKeySounds = getContext().getResources().getBoolean( - com.android.internal.R.bool.config_useVolumeKeySounds); - mUseFixedVolume = getContext().getResources().getBoolean( - com.android.internal.R.bool.config_useFixedVolume); } private Context getContext() { @@ -823,6 +817,18 @@ public class AudioManager { * </ul> */ public boolean isVolumeFixed() { + synchronized (this) { + try { + if (!mUseFixedVolumeInitialized) { + mUseFixedVolume = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_useFixedVolume); + } + } catch (Exception e) { + } finally { + // only ever try once, so always consider initialized even if query failed + mUseFixedVolumeInitialized = true; + } + } return mUseFixedVolume; } diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java index e09241137541..4011de9407f3 100644 --- a/media/java/android/media/MediaTranscodeManager.java +++ b/media/java/android/media/MediaTranscodeManager.java @@ -83,13 +83,13 @@ import java.util.concurrent.Executors; <pre class=prettyprint> TranscodingRequest request = - new TranscodingRequest.Builder() - .setSourceUri(srcUri) - .setDestinationUri(dstUri) - .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) - .setPriority(REALTIME) - .setVideoTrackFormat(videoFormat) - .build(); + new TranscodingRequest.Builder() + .setSourceUri(srcUri) + .setDestinationUri(dstUri) + .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) + .setPriority(REALTIME) + .setVideoTrackFormat(videoFormat) + .build(); }</pre> TODO(hkuang): Add architecture diagram showing the transcoding service and api. @@ -498,6 +498,20 @@ public final class MediaTranscodeManager { /** Uri of the destination media file. */ private @NonNull Uri mDestinationUri; + /** + * The UID of the client that the TranscodingRequest is for. Only privileged caller could + * set this Uid as only they could do the transcoding on behalf of the client. + * -1 means not available. + */ + private int mClientUid = -1; + + /** + * The Pid of the client that the TranscodingRequest is for. Only privileged caller could + * set this Uid as only they could do the transcoding on behalf of the client. + * -1 means not available. + */ + private int mClientPid = -1; + /** Type of the transcoding. */ private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; @@ -534,6 +548,8 @@ public final class MediaTranscodeManager { private TranscodingRequest(Builder b) { mSourceUri = b.mSourceUri; mDestinationUri = b.mDestinationUri; + mClientUid = b.mClientUid; + mClientPid = b.mClientPid; mPriority = b.mPriority; mType = b.mType; mVideoTrackFormat = b.mVideoTrackFormat; @@ -554,6 +570,16 @@ public final class MediaTranscodeManager { return mSourceUri; } + /** Return the UID of the client that this request is for. -1 means not available. */ + public int getClientUid() { + return mClientUid; + } + + /** Return the PID of the client that this request is for. -1 means not available. */ + public int getClientPid() { + return mClientPid; + } + /** Return destination uri of the transcoding. */ @NonNull public Uri getDestinationUri() { @@ -592,6 +618,8 @@ public final class MediaTranscodeManager { parcel.transcodingType = mType; parcel.sourceFilePath = mSourceUri.toString(); parcel.destinationFilePath = mDestinationUri.toString(); + parcel.clientUid = mClientUid; + parcel.clientPid = mClientPid; parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat); if (mTestConfig != null) { parcel.isForTesting = true; @@ -667,6 +695,8 @@ public final class MediaTranscodeManager { public static final class Builder { private @NonNull Uri mSourceUri; private @NonNull Uri mDestinationUri; + private int mClientUid = -1; + private int mClientPid = -1; private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN; private @Nullable MediaFormat mVideoTrackFormat; @@ -710,6 +740,38 @@ public final class MediaTranscodeManager { } /** + * Specify the UID of the client that this request is for. + * @param uid client Uid. + * @return The same builder instance. + * @throws IllegalArgumentException if uid is invalid. + * TODO(hkuang): Check the permission if it is allowed. + */ + @NonNull + public Builder setClientUid(int uid) { + if (uid <= 0) { + throw new IllegalArgumentException("Invalid Uid"); + } + mClientUid = uid; + return this; + } + + /** + * Specify the PID of the client that this request is for. + * @param pid client Pid. + * @return The same builder instance. + * @throws IllegalArgumentException if pid is invalid. + * TODO(hkuang): Check the permission if it is allowed. + */ + @NonNull + public Builder setClientPid(int pid) { + if (pid <= 0) { + throw new IllegalArgumentException("Invalid pid"); + } + mClientPid = pid; + return this; + } + + /** * Specifies the priority of the transcoding. * * @param priority Must be one of the {@code PRIORITY_*} @@ -1275,6 +1337,8 @@ public final class MediaTranscodeManager { // Converts the request to TranscodingRequestParcel. TranscodingRequestParcel requestParcel = transcodingRequest.writeToParcel(); + Log.i(TAG, "Getting transcoding request " + transcodingRequest.getSourceUri()); + // Submits the request to MediaTranscoding service. try { TranscodingJobParcel jobParcel = new TranscodingJobParcel(); diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java index 33d6d64c7f37..21ed840c7313 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java @@ -203,6 +203,42 @@ public class MediaTranscodeManagerTest } /** + * Verify that setting invalid pid will throw exception. + */ + @Test + public void testCreateTranscodingWithInvalidClientPid() throws Exception { + assertThrows(IllegalArgumentException.class, () -> { + TranscodingRequest request = + new TranscodingRequest.Builder() + .setSourceUri(mSourceHEVCVideoUri) + .setDestinationUri(mDestinationUri) + .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) + .setPriority(MediaTranscodeManager.PRIORITY_REALTIME) + .setClientPid(-1) + .setVideoTrackFormat(createMediaFormat()) + .build(); + }); + } + + /** + * Verify that setting invalid uid will throw exception. + */ + @Test + public void testCreateTranscodingWithInvalidClientUid() throws Exception { + assertThrows(IllegalArgumentException.class, () -> { + TranscodingRequest request = + new TranscodingRequest.Builder() + .setSourceUri(mSourceHEVCVideoUri) + .setDestinationUri(mDestinationUri) + .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) + .setPriority(MediaTranscodeManager.PRIORITY_REALTIME) + .setClientUid(-1) + .setVideoTrackFormat(createMediaFormat()) + .build(); + }); + } + + /** * Verify that setting null source uri will throw exception. */ @Test @@ -425,16 +461,24 @@ public class MediaTranscodeManagerTest MediaFormat videoTrackFormat = resolver.resolveVideoFormat(); assertNotNull(videoTrackFormat); + int pid = android.os.Process.myPid(); + int uid = android.os.Process.myUid(); + TranscodingRequest request = new TranscodingRequest.Builder() .setSourceUri(mSourceHEVCVideoUri) .setDestinationUri(destinationUri) .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) + .setClientPid(pid) + .setClientUid(uid) .setPriority(MediaTranscodeManager.PRIORITY_REALTIME) .setVideoTrackFormat(videoTrackFormat) .build(); Executor listenerExecutor = Executors.newSingleThreadExecutor(); + assertEquals(pid, request.getClientPid()); + assertEquals(uid, request.getClientUid()); + Log.i(TAG, "transcoding to " + videoTrackFormat); TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor, diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 80300611c7f2..95a32544ff68 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -6138,6 +6138,7 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR; field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000 field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000 + field public static final int FLAG_MUTABLE = 33554432; // 0x2000000 field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000 field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000 field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000 @@ -14292,6 +14293,7 @@ package android.graphics { public final class BlurShader extends android.graphics.Shader { ctor public BlurShader(float, float, @Nullable android.graphics.Shader); + ctor public BlurShader(float, float, @Nullable android.graphics.Shader, @NonNull android.graphics.Shader.TileMode); } public class Camera { @@ -15177,6 +15179,20 @@ package android.graphics { ctor public PaintFlagsDrawFilter(int, int); } + public final class ParcelableColorSpace extends android.graphics.ColorSpace implements android.os.Parcelable { + ctor public ParcelableColorSpace(@NonNull android.graphics.ColorSpace); + method public int describeContents(); + method @NonNull public float[] fromXyz(@NonNull float[]); + method @NonNull public android.graphics.ColorSpace getColorSpace(); + method public float getMaxValue(int); + method public float getMinValue(int); + method public static boolean isParcelable(@NonNull android.graphics.ColorSpace); + method public boolean isWideGamut(); + method @NonNull public float[] toXyz(@NonNull float[]); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.graphics.ParcelableColorSpace> CREATOR; + } + public class Path { ctor public Path(); ctor public Path(@Nullable android.graphics.Path); @@ -15612,6 +15628,7 @@ package android.graphics { public enum Shader.TileMode { enum_constant public static final android.graphics.Shader.TileMode CLAMP; + enum_constant public static final android.graphics.Shader.TileMode DECAL; enum_constant public static final android.graphics.Shader.TileMode MIRROR; enum_constant public static final android.graphics.Shader.TileMode REPEAT; } diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index 10fe058bb76f..e27ca09f8e86 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -4339,6 +4339,8 @@ package android.media { } public static final class MediaTranscodeManager.TranscodingRequest { + method public int getClientPid(); + method public int getClientUid(); method @NonNull public android.net.Uri getDestinationUri(); method public int getPriority(); method @NonNull public android.net.Uri getSourceUri(); @@ -4349,6 +4351,8 @@ package android.media { public static final class MediaTranscodeManager.TranscodingRequest.Builder { ctor public MediaTranscodeManager.TranscodingRequest.Builder(); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml index fbdb5167fade..06ae7cfd6d1b 100644 --- a/packages/CarSystemUI/res/values/strings.xml +++ b/packages/CarSystemUI/res/values/strings.xml @@ -22,6 +22,8 @@ <string name="hvac_min_text">Min</string> <!-- String to represent largest setting of an HVAC system [CHAR LIMIT=10]--> <string name="hvac_max_text">Max</string> + <!-- String to display when no HVAC temperature is available --> + <string name="hvac_null_temp_text" translatable="false">--</string> <!-- Text for voice recognition toast. [CHAR LIMIT=60] --> <string name="voice_recognition_toast">Voice recognition now handled by connected Bluetooth device</string> <!-- Name of Guest Profile. [CHAR LIMIT=35] --> diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java index 85d4ceb81eeb..af2a1d36bbd7 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java @@ -40,6 +40,9 @@ public class AdjustableTemperatureView extends LinearLayout implements Temperatu private float mMinTempC; private float mMaxTempC; private String mTempFormat; + private String mNullTempText; + private String mMinTempText; + private String mMaxTempText; private boolean mDisplayInFahrenheit = false; private HvacController mHvacController; @@ -59,6 +62,9 @@ public class AdjustableTemperatureView extends LinearLayout implements Temperatu mTempFormat = getResources().getString(R.string.hvac_temperature_format); mMinTempC = getResources().getFloat(R.dimen.hvac_min_value_celsius); mMaxTempC = getResources().getFloat(R.dimen.hvac_max_value_celsius); + mNullTempText = getResources().getString(R.string.hvac_null_temp_text); + mMinTempText = getResources().getString(R.string.hvac_min_text); + mMaxTempText = getResources().getString(R.string.hvac_max_text); initializeButtons(); } @@ -69,12 +75,23 @@ public class AdjustableTemperatureView extends LinearLayout implements Temperatu @Override public void setTemp(float tempC) { - if (tempC > mMaxTempC || tempC < mMinTempC) { - return; - } if (mTempTextView == null) { mTempTextView = findViewById(R.id.hvac_temperature_text); } + if (Float.isNaN(tempC)) { + mTempTextView.setText(mNullTempText); + return; + } + if (tempC <= mMinTempC) { + mTempTextView.setText(mMinTempText); + mCurrentTempC = mMinTempC; + return; + } + if (tempC >= mMaxTempC) { + mTempTextView.setText(mMaxTempText); + mCurrentTempC = mMaxTempC; + return; + } mTempTextView.setText(String.format(mTempFormat, mDisplayInFahrenheit ? convertToFahrenheit(tempC) : tempC)); mCurrentTempC = tempC; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java index a3a55aae5f18..fe071d54fb10 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java @@ -98,6 +98,48 @@ public class AdjustableTemperatureViewTest extends SysuiTestCase { } @Test + public void setTemp_tempNaN_setsTextToNaNText() { + when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET), + anyInt())).thenReturn(true); + when(mCarPropertyManager.getFloatProperty(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn( + Float.NaN); + + mHvacController.addTemperatureViewToController(mAdjustableTemperatureView); + + TextView tempText = mAdjustableTemperatureView.findViewById(R.id.hvac_temperature_text); + assertEquals(tempText.getText(), + getContext().getResources().getString(R.string.hvac_null_temp_text)); + } + + @Test + public void setTemp_tempBelowMin_setsTextToMinTempText() { + when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET), + anyInt())).thenReturn(true); + when(mCarPropertyManager.getFloatProperty(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn( + getContext().getResources().getFloat(R.dimen.hvac_min_value_celsius)); + + mHvacController.addTemperatureViewToController(mAdjustableTemperatureView); + + TextView tempText = mAdjustableTemperatureView.findViewById(R.id.hvac_temperature_text); + assertEquals(tempText.getText(), + getContext().getResources().getString(R.string.hvac_min_text)); + } + + @Test + public void setTemp_tempAboveMax_setsTextToMaxTempText() { + when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET), + anyInt())).thenReturn(true); + when(mCarPropertyManager.getFloatProperty(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn( + getContext().getResources().getFloat(R.dimen.hvac_max_value_celsius)); + + mHvacController.addTemperatureViewToController(mAdjustableTemperatureView); + + TextView tempText = mAdjustableTemperatureView.findViewById(R.id.hvac_temperature_text); + assertEquals(tempText.getText(), + getContext().getResources().getString(R.string.hvac_max_text)); + } + + @Test public void setTemperatureToFahrenheit_callsViewSetDisplayInFahrenheit() { when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn(true); diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index 0229e6e9d4dd..73beefc9da83 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -32,7 +32,7 @@ android:id="@+id/header_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingEnd="16dp"/> + android:paddingEnd="@dimen/media_output_dialog_header_icon_padding"/> <LinearLayout android:layout_width="match_parent" @@ -70,36 +70,14 @@ android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="start|center_vertical" android:orientation="vertical"> - <View - android:layout_width="match_parent" - android:layout_height="12dp"/> - - <include - layout="@layout/media_output_list_item" - android:id="@+id/group_item_controller" - android:visibility="gone"/> - - <View - android:id="@+id/group_item_divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:background="?android:attr/listDivider" - android:visibility="gone"/> - <androidx.recyclerview.widget.RecyclerView android:id="@+id/list_result" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:overScrollMode="never"/> - - <View - android:id="@+id/list_bottom_padding" - android:layout_width="match_parent" - android:layout_height="12dp"/> </LinearLayout> <View diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml index 6ced97836358..efd24c7d9d4f 100644 --- a/packages/SystemUI/res/layout/window_magnifier_view.xml +++ b/packages/SystemUI/res/layout/window_magnifier_view.xml @@ -17,24 +17,26 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="wrap_content"> - + android:layout_height="wrap_content" + android:screenReaderFocusable="true"> <View android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/magnification_outer_border_margin" + android:importantForAccessibility="no" android:background="@android:color/black"/> <View android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/magnification_inner_border_margin" + android:importantForAccessibility="no" android:background="@color/magnification_border_color"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> + android:importantForAccessibility="noHideDescendants"> <View android:id="@+id/left_handle" @@ -76,6 +78,7 @@ android:layout_margin="@dimen/magnification_outer_border_margin" android:layout_gravity="right|bottom" android:scaleType="center" + android:importantForAccessibility="no" android:src="@drawable/ic_move_magnification"/> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 98e8cde40275..6bce36a51de0 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1368,9 +1368,10 @@ <dimen name="config_rounded_mask_size_bottom">@*android:dimen/rounded_corner_radius_bottom</dimen> <!-- Output switcher panel related dimensions --> - <dimen name="media_output_dialog_padding_top">11dp</dimen> + <dimen name="media_output_dialog_list_margin">12dp</dimen> <dimen name="media_output_dialog_list_max_height">364dp</dimen> <dimen name="media_output_dialog_header_album_icon_size">52dp</dimen> <dimen name="media_output_dialog_header_back_icon_size">36dp</dimen> + <dimen name="media_output_dialog_header_icon_padding">16dp</dimen> <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index 23195af8bdea..e99245fa438f 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -33,13 +33,9 @@ import android.view.SurfaceView; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; -import com.android.keyguard.dagger.KeyguardBouncerScope; -import com.android.systemui.dagger.qualifiers.Main; import java.util.NoSuchElementException; -import javax.inject.Inject; - /** * Encapsulates all logic for secondary lockscreen state management. */ @@ -146,9 +142,9 @@ public class AdminSecondaryLockScreenController { } }; - private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent, + public AdminSecondaryLockScreenController(Context context, ViewGroup parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, - @Main Handler handler) { + Handler handler) { mContext = context; mHandler = handler; mParent = parent; @@ -238,26 +234,4 @@ public class AdminSecondaryLockScreenController { getHolder().removeCallback(mSurfaceHolderCallback); } } - - @KeyguardBouncerScope - public static class Factory { - private final Context mContext; - private final KeyguardSecurityContainer mParent; - private final KeyguardUpdateMonitor mUpdateMonitor; - private final Handler mHandler; - - @Inject - public Factory(Context context, KeyguardSecurityContainer parent, - KeyguardUpdateMonitor updateMonitor, @Main Handler handler) { - mContext = context; - mParent = parent; - mUpdateMonitor = updateMonitor; - mHandler = handler; - } - - public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) { - return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor, - callback, mHandler); - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index cc6df45c598f..88f4176f5eac 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -16,26 +16,46 @@ package com.android.keyguard; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; + import android.content.Context; +import android.content.res.ColorStateList; +import android.os.AsyncTask; +import android.os.CountDownTimer; +import android.os.SystemClock; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.View; +import android.widget.LinearLayout; +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; +import com.android.systemui.Dependency; import com.android.systemui.R; /** * Base class for PIN and password unlock screens. */ -public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { +public abstract class KeyguardAbsKeyInputView extends LinearLayout + implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback { + protected KeyguardSecurityCallback mCallback; + protected LockPatternUtils mLockPatternUtils; + protected AsyncTask<?, ?, ?> mPendingLockCheck; + protected SecurityMessageDisplay mSecurityMessageDisplay; protected View mEcaView; protected boolean mEnableHaptics; + private boolean mDismissing; + protected boolean mResumed; + private CountDownTimer mCountdownTimer = null; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; - private KeyDownListener mKeyDownListener; public KeyguardAbsKeyInputView(Context context) { this(context, null); @@ -43,10 +63,38 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) { super(context, attrs); + mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } - void setEnableHaptics(boolean enableHaptics) { - mEnableHaptics = enableHaptics; + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled(); + } + + @Override + public void reset() { + // start fresh + mDismissing = false; + resetPasswordText(false /* animate */, false /* announce */); + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser()); + if (shouldLockout(deadline)) { + handleAttemptLockout(deadline); + } else { + resetState(); + } + } + + // Allow subclasses to override this behavior + protected boolean shouldLockout(long deadline) { + return deadline != 0; } protected abstract int getPasswordTextViewId(); @@ -54,7 +102,24 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { @Override protected void onFinishInflate() { + mLockPatternUtils = new LockPatternUtils(mContext); mEcaView = findViewById(R.id.keyguard_selector_fade_container); + + EmergencyButton button = findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(this); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this); + } + + @Override + public void onEmergencyButtonClickedWhenInCall() { + mCallback.reset(); } /* @@ -66,14 +131,195 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { return R.string.kg_wrong_password; } + protected void verifyPasswordAndUnlock() { + if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. + + final LockscreenCredential password = getEnteredCredential(); + setPasswordEntryInputEnabled(false); + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + final int userId = KeyguardUpdateMonitor.getCurrentUser(); + if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + setPasswordEntryInputEnabled(true); + onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); + password.zeroize(); + return; + } + + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); + LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + + mKeyguardUpdateMonitor.setCredentialAttempted(); + mPendingLockCheck = LockPatternChecker.checkCredential( + mLockPatternUtils, + password, + userId, + new LockPatternChecker.OnCheckCallback() { + + @Override + public void onEarlyMatched() { + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL); + } + onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, + true /* isValidPassword */); + password.zeroize(); + } + + @Override + public void onChecked(boolean matched, int timeoutMs) { + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + setPasswordEntryInputEnabled(true); + mPendingLockCheck = null; + if (!matched) { + onPasswordChecked(userId, false /* matched */, timeoutMs, + true /* isValidPassword */); + } + password.zeroize(); + } + + @Override + public void onCancelled() { + // We already got dismissed with the early matched callback, so we cancelled + // the check. However, we still need to note down the latency. + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + password.zeroize(); + } + }); + } + + private void onPasswordChecked(int userId, boolean matched, int timeoutMs, + boolean isValidPassword) { + boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + if (matched) { + mCallback.reportUnlockAttempt(userId, true, 0); + if (dismissKeyguard) { + mDismissing = true; + mCallback.dismiss(true, userId); + } + } else { + if (isValidPassword) { + mCallback.reportUnlockAttempt(userId, false, timeoutMs); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + userId, timeoutMs); + handleAttemptLockout(deadline); + } + } + if (timeoutMs == 0) { + mSecurityMessageDisplay.setMessage(getWrongPasswordStringId()); + } + } + resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); + } + protected abstract void resetPasswordText(boolean animate, boolean announce); protected abstract LockscreenCredential getEnteredCredential(); protected abstract void setPasswordEntryEnabled(boolean enabled); protected abstract void setPasswordEntryInputEnabled(boolean enabled); + // Prevent user from using the PIN/Password entry until scheduled deadline. + protected void handleAttemptLockout(long elapsedRealtimeDeadline) { + setPasswordEntryEnabled(false); + long elapsedRealtime = SystemClock.elapsedRealtime(); + long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); + mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); + } + + @Override + public void onFinish() { + mSecurityMessageDisplay.setMessage(""); + resetState(); + } + }.start(); + } + + protected void onUserInput() { + if (mCallback != null) { + mCallback.userActivity(); + mCallback.onUserInput(); + } + mSecurityMessageDisplay.setMessage(""); + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - return mKeyDownListener != null && mKeyDownListener.onKeyDown(keyCode, event); + // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. + // We don't want to consider it valid user input because the UI + // will already respond to the event. + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + onUserInput(); + } + return false; + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + mResumed = false; + + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + mPendingLockCheck = null; + } + reset(); + } + + @Override + public void onResume(int reason) { + mResumed = true; + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + + @Override + public void showPromptReason(int reason) { + if (reason != PROMPT_REASON_NONE) { + int promtReasonStringRes = getPromptReasonStringRes(reason); + if (promtReasonStringRes != 0) { + mSecurityMessageDisplay.setMessage(promtReasonStringRes); + } + } + } + + @Override + public void showMessage(CharSequence message, ColorStateList colorState) { + if (colorState != null) { + mSecurityMessageDisplay.setNextMessageColor(colorState); + } + mSecurityMessageDisplay.setMessage(message); } protected abstract int getPromptReasonStringRes(int reason); @@ -87,12 +333,9 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { } } - public void setKeyDownListener(KeyDownListener keyDownListener) { - mKeyDownListener = keyDownListener; - } - - public interface KeyDownListener { - boolean onKeyDown(int keyCode, KeyEvent keyEvent); + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return false; } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java deleted file mode 100644 index d957628c52ab..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; -import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT; - -import android.content.res.ColorStateList; -import android.os.AsyncTask; -import android.os.CountDownTimer; -import android.os.SystemClock; -import android.view.KeyEvent; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternChecker; -import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.LockscreenCredential; -import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; -import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; - -public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView> - extends KeyguardInputViewController<T> { - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final LockPatternUtils mLockPatternUtils; - private final LatencyTracker mLatencyTracker; - private CountDownTimer mCountdownTimer; - protected KeyguardMessageAreaController mMessageAreaController; - private boolean mDismissing; - protected AsyncTask<?, ?, ?> mPendingLockCheck; - protected boolean mResumed; - - private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> { - // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. - // We don't want to consider it valid user input because the UI - // will already respond to the event. - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - onUserInput(); - } - return false; - }; - - private final EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { - @Override - public void onEmergencyButtonClickedWhenInCall() { - getKeyguardSecurityCallback().reset(); - } - }; - - protected KeyguardAbsKeyInputViewController(T view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, - LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker) { - super(view, securityMode, keyguardSecurityCallback); - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mLockPatternUtils = lockPatternUtils; - mLatencyTracker = latencyTracker; - KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); - mMessageAreaController = messageAreaControllerFactory.create(kma); - } - - abstract void resetState(); - - @Override - public void init() { - super.init(); - mMessageAreaController.init(); - } - - @Override - protected void onViewAttached() { - mView.setKeyDownListener(mKeyDownListener); - mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); - EmergencyButton button = mView.findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(mEmergencyButtonCallback); - } - } - - @Override - public void reset() { - // start fresh - mDismissing = false; - mView.resetPasswordText(false /* animate */, false /* announce */); - // if the user is currently locked out, enforce it. - long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); - if (shouldLockout(deadline)) { - handleAttemptLockout(deadline); - } else { - mView.resetState(); - } - } - - @Override - public boolean needsInput() { - return false; - } - - @Override - public void showMessage(CharSequence message, ColorStateList colorState) { - if (colorState != null) { - mMessageAreaController.setNextMessageColor(colorState); - } - mMessageAreaController.setMessage(message); - } - - // Allow subclasses to override this behavior - protected boolean shouldLockout(long deadline) { - return deadline != 0; - } - - // Prevent user from using the PIN/Password entry until scheduled deadline. - protected void handleAttemptLockout(long elapsedRealtimeDeadline) { - mView.setPasswordEntryEnabled(false); - long elapsedRealtime = SystemClock.elapsedRealtime(); - long secondsInFuture = (long) Math.ceil( - (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); - mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { - - @Override - public void onTick(long millisUntilFinished) { - int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mMessageAreaController.setMessage(mView.getResources().getQuantityString( - R.plurals.kg_too_many_failed_attempts_countdown, - secondsRemaining, secondsRemaining)); - } - - @Override - public void onFinish() { - mMessageAreaController.setMessage(""); - resetState(); - } - }.start(); - } - - void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; - if (matched) { - getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); - if (dismissKeyguard) { - mDismissing = true; - getKeyguardSecurityCallback().dismiss(true, userId); - } - } else { - if (isValidPassword) { - getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); - if (timeoutMs > 0) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - userId, timeoutMs); - handleAttemptLockout(deadline); - } - } - if (timeoutMs == 0) { - mMessageAreaController.setMessage(mView.getWrongPasswordStringId()); - } - } - mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); - } - - protected void verifyPasswordAndUnlock() { - if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. - - final LockscreenCredential password = mView.getEnteredCredential(); - mView.setPasswordEntryInputEnabled(false); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - final int userId = KeyguardUpdateMonitor.getCurrentUser(); - if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { - // to avoid accidental lockout, only count attempts that are long enough to be a - // real password. This may require some tweaking. - mView.setPasswordEntryInputEnabled(true); - onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); - password.zeroize(); - return; - } - - mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL); - mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); - - mKeyguardUpdateMonitor.setCredentialAttempted(); - mPendingLockCheck = LockPatternChecker.checkCredential( - mLockPatternUtils, - password, - userId, - new LockPatternChecker.OnCheckCallback() { - - @Override - public void onEarlyMatched() { - mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL); - - onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, - true /* isValidPassword */); - password.zeroize(); - } - - @Override - public void onChecked(boolean matched, int timeoutMs) { - mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); - mView.setPasswordEntryInputEnabled(true); - mPendingLockCheck = null; - if (!matched) { - onPasswordChecked(userId, false /* matched */, timeoutMs, - true /* isValidPassword */); - } - password.zeroize(); - } - - @Override - public void onCancelled() { - // We already got dismissed with the early matched callback, so we cancelled - // the check. However, we still need to note down the latency. - mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); - password.zeroize(); - } - }); - } - - @Override - public void showPromptReason(int reason) { - if (reason != PROMPT_REASON_NONE) { - int promtReasonStringRes = mView.getPromptReasonStringRes(reason); - if (promtReasonStringRes != 0) { - mMessageAreaController.setMessage(promtReasonStringRes); - } - } - } - - protected void onUserInput() { - getKeyguardSecurityCallback().userActivity(); - getKeyguardSecurityCallback().onUserInput(); - mMessageAreaController.setMessage(""); - } - - @Override - public void onResume(int reason) { - mResumed = true; - } - - @Override - public void onPause() { - mResumed = false; - - if (mCountdownTimer != null) { - mCountdownTimer.cancel(); - mCountdownTimer = null; - } - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; - } - reset(); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 36d5543f1c01..be21d203411e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -39,6 +39,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; +import com.android.systemui.util.InjectionInflationController; import javax.inject.Inject; @@ -48,6 +49,7 @@ public class KeyguardDisplayManager { private final MediaRouter mMediaRouter; private final DisplayManager mDisplayService; + private final InjectionInflationController mInjectableInflater; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; private final Context mContext; @@ -90,8 +92,10 @@ public class KeyguardDisplayManager { @Inject public KeyguardDisplayManager(Context context, + InjectionInflationController injectableInflater, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) { mContext = context; + mInjectableInflater = injectableInflater; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; mMediaRouter = mContext.getSystemService(MediaRouter.class); mDisplayService = mContext.getSystemService(DisplayManager.class); @@ -127,7 +131,8 @@ public class KeyguardDisplayManager { Presentation presentation = mPresentations.get(displayId); if (presentation == null) { final Presentation newPresentation = new KeyguardPresentation(mContext, display, - mKeyguardStatusViewComponentFactory, LayoutInflater.from(mContext)); + mKeyguardStatusViewComponentFactory, + mInjectableInflater.injectable(LayoutInflater.from(mContext))); newPresentation.setOnDismissListener(dialog -> { if (newPresentation.equals(mPresentations.get(displayId))) { mPresentations.remove(displayId); @@ -245,7 +250,7 @@ public class KeyguardDisplayManager { private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - private final LayoutInflater mLayoutInflater; + private final LayoutInflater mInjectableLayoutInflater; private KeyguardClockSwitchController mKeyguardClockSwitchController; private View mClock; private int mUsableWidth; @@ -265,10 +270,10 @@ public class KeyguardDisplayManager { KeyguardPresentation(Context context, Display display, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, - LayoutInflater layoutInflater) { + LayoutInflater injectionLayoutInflater) { super(context, display, R.style.Theme_SystemUI_KeyguardPresentation); mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; - mLayoutInflater = layoutInflater; + mInjectableLayoutInflater = injectionLayoutInflater; getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); setCancelable(false); } @@ -294,7 +299,7 @@ public class KeyguardDisplayManager { mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200; mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200; - setContentView(mLayoutInflater.inflate(R.layout.keyguard_presentation, null)); + setContentView(mInjectableLayoutInflater.inflate(R.layout.keyguard_presentation, null)); // Logic to make the lock screen fullscreen getWindow().getDecorView().setSystemUiVisibility( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 9ffa658da0e8..7aabb17de90c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -178,18 +178,18 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> /** Initialize the Controller. */ public void init() { super.init(); + mView.setViewMediatorCallback(mViewMediatorCallback); + // Update ViewMediator with the current input method requirements + mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); mKeyguardSecurityContainerController.init(); + mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback); + mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); } @Override protected void onViewAttached() { - mView.setViewMediatorCallback(mViewMediatorCallback); - // Update ViewMediator with the current input method requirements - mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); mView.setOnKeyListener(mOnKeyListener); - mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback); - mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); } @Override @@ -350,7 +350,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> } public boolean handleBackKey() { - if (mKeyguardSecurityContainerController.getCurrentSecurityMode() + if (mKeyguardSecurityContainerController.getCurrentSecuritySelection() != SecurityMode.None) { mKeyguardSecurityContainerController.dismiss( false, KeyguardUpdateMonitor.getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java deleted file mode 100644 index d42a53cc875e..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.LinearLayout; - -import androidx.annotation.Nullable; - -/** - * A Base class for all Keyguard password/pattern/pin related inputs. - */ -public abstract class KeyguardInputView extends LinearLayout { - - public KeyguardInputView(Context context) { - super(context); - } - - public KeyguardInputView(Context context, - @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public KeyguardInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - abstract CharSequence getTitle(); - - boolean disallowInterceptTouch(MotionEvent event) { - return false; - } - - void startAppearAnimation() {} - - boolean startDisappearAnimation(Runnable finishRunnable) { - return false; - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java deleted file mode 100644 index fbda818740e8..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.telephony.TelephonyManager; -import android.view.inputmethod.InputMethodManager; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.ViewController; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import javax.inject.Inject; - - -/** Controller for a {@link KeyguardSecurityView}. */ -public abstract class KeyguardInputViewController<T extends KeyguardInputView> - extends ViewController<T> implements KeyguardSecurityView { - - private final SecurityMode mSecurityMode; - private final KeyguardSecurityCallback mKeyguardSecurityCallback; - private boolean mPaused; - - - // The following is used to ignore callbacks from SecurityViews that are no longer current - // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the - // state for the current security method. - private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { - @Override - public void userActivity() { } - @Override - public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { } - @Override - public boolean isVerifyUnlockOnly() { - return false; - } - @Override - public void dismiss(boolean securityVerified, int targetUserId) { } - @Override - public void dismiss(boolean authenticated, int targetId, - boolean bypassSecondaryLockScreen) { } - @Override - public void onUserInput() { } - @Override - public void reset() {} - }; - - protected KeyguardInputViewController(T view, SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback) { - super(view); - mSecurityMode = securityMode; - mKeyguardSecurityCallback = keyguardSecurityCallback; - } - - @Override - protected void onViewAttached() { - } - - @Override - protected void onViewDetached() { - } - - SecurityMode getSecurityMode() { - return mSecurityMode; - } - - protected KeyguardSecurityCallback getKeyguardSecurityCallback() { - if (mPaused) { - return mNullCallback; - } - - return mKeyguardSecurityCallback; - } - - @Override - public void reset() { - } - - @Override - public void onPause() { - mPaused = true; - } - - @Override - public void onResume(int reason) { - mPaused = false; - } - - @Override - public void showPromptReason(int reason) { - } - - @Override - public void showMessage(CharSequence message, ColorStateList colorState) { - } - - public void startAppearAnimation() { - mView.startAppearAnimation(); - } - - public boolean startDisappearAnimation(Runnable finishRunnable) { - return mView.startDisappearAnimation(finishRunnable); - } - - @Override - public CharSequence getTitle() { - return mView.getTitle(); - } - - /** Finds the index of this view in the suppplied parent view. */ - public int getIndexIn(KeyguardSecurityViewFlipper view) { - return view.indexOfChild(mView); - } - - /** Factory for a {@link KeyguardInputViewController}. */ - public static class Factory { - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final LockPatternUtils mLockPatternUtils; - private final LatencyTracker mLatencyTracker; - private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; - private final InputMethodManager mInputMethodManager; - private final DelayableExecutor mMainExecutor; - private final Resources mResources; - private LiftToActivateListener mLiftToActivateListener; - private TelephonyManager mTelephonyManager; - - @Inject - public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, - LockPatternUtils lockPatternUtils, - LatencyTracker latencyTracker, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, - @Main Resources resources, LiftToActivateListener liftToActivateListener, - TelephonyManager telephonyManager) { - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mLockPatternUtils = lockPatternUtils; - mLatencyTracker = latencyTracker; - mMessageAreaControllerFactory = messageAreaControllerFactory; - mInputMethodManager = inputMethodManager; - mMainExecutor = mainExecutor; - mResources = resources; - mLiftToActivateListener = liftToActivateListener; - mTelephonyManager = telephonyManager; - } - - /** Create a new {@link KeyguardInputViewController}. */ - public KeyguardInputViewController create(KeyguardInputView keyguardInputView, - SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) { - if (keyguardInputView instanceof KeyguardPatternView) { - return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView, - mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, - keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory); - } else if (keyguardInputView instanceof KeyguardPasswordView) { - return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, - mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, - keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mInputMethodManager, mMainExecutor, mResources); - } else if (keyguardInputView instanceof KeyguardPINView) { - return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, - mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, - keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener); - } else if (keyguardInputView instanceof KeyguardSimPinView) { - return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, - mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, - keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mTelephonyManager); - } else if (keyguardInputView instanceof KeyguardSimPukView) { - return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, - mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, - keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mTelephonyManager); - } - - throw new RuntimeException("Unable to find controller for " + keyguardInputView); - } - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index 1a0a4370fca4..a8b1451d92c7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; + import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -29,14 +31,20 @@ import android.util.TypedValue; import android.view.View; import android.widget.TextView; +import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ConfigurationController; import java.lang.ref.WeakReference; +import javax.inject.Inject; +import javax.inject.Named; + /*** * Manages a number of views inside of the given layout. See below for a list of widgets. */ -public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay { +public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay, + ConfigurationController.ConfigurationListener { /** Handler token posted with accessibility announcement runnables. */ private static final Object ANNOUNCE_TOKEN = new Object(); @@ -48,26 +56,71 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp private static final int DEFAULT_COLOR = -1; private final Handler mHandler; + private final ConfigurationController mConfigurationController; private ColorStateList mDefaultColorState; private CharSequence mMessage; private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); private boolean mBouncerVisible; - public KeyguardMessageArea(Context context, AttributeSet attrs) { + private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { + public void onFinishedGoingToSleep(int why) { + setSelected(false); + } + + public void onStartedWakingUp() { + setSelected(true); + } + + @Override + public void onKeyguardBouncerChanged(boolean bouncer) { + mBouncerVisible = bouncer; + update(); + } + }; + + public KeyguardMessageArea(Context context) { + super(context, null); + throw new IllegalStateException("This constructor should never be invoked"); + } + + @Inject + public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, + ConfigurationController configurationController) { + this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController); + } + + public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor, + ConfigurationController configurationController) { super(context, attrs); setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug + monitor.registerCallback(mInfoCallback); mHandler = new Handler(Looper.myLooper()); + mConfigurationController = configurationController; onThemeChanged(); } @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mConfigurationController.addCallback(this); + onThemeChanged(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mConfigurationController.removeCallback(this); + } + + @Override public void setNextMessageColor(ColorStateList colorState) { mNextMessageColorState = colorState; } - void onThemeChanged() { + @Override + public void onThemeChanged() { TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); @@ -77,7 +130,8 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp update(); } - void onDensityOrFontScaleChanged() { + @Override + public void onDensityOrFontScaleChanged() { TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] { android.R.attr.textSize }); @@ -123,6 +177,12 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp return messageArea; } + @Override + protected void onFinishInflate() { + boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); + setSelected(shouldMarquee); // This is required to ensure marquee works + } + private void securityMessageChanged(CharSequence message) { mMessage = message; update(); @@ -136,7 +196,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp update(); } - void update() { + private void update() { CharSequence status = mMessage; setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE); setText(status); @@ -148,9 +208,6 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp setTextColor(colorState); } - public void setBouncerVisible(boolean bouncerVisible) { - mBouncerVisible = bouncerVisible; - } /** * Runnable used to delay accessibility announcements. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 1618e8e58055..f056bdbb9706 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -16,10 +16,7 @@ package com.android.keyguard; -import android.content.res.ColorStateList; - import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -29,35 +26,6 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; - - private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - public void onFinishedGoingToSleep(int why) { - mView.setSelected(false); - } - - public void onStartedWakingUp() { - mView.setSelected(true); - } - - @Override - public void onKeyguardBouncerChanged(boolean bouncer) { - mView.setBouncerVisible(bouncer); - mView.update(); - } - }; - - private ConfigurationListener mConfigurationListener = new ConfigurationListener() { - @Override - public void onThemeChanged() { - mView.onThemeChanged(); - } - - @Override - public void onDensityOrFontScaleChanged() { - mView.onDensityOrFontScaleChanged(); - } - }; - private KeyguardMessageAreaController(KeyguardMessageArea view, KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController) { @@ -69,31 +37,17 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag @Override protected void onViewAttached() { - mConfigurationController.addCallback(mConfigurationListener); - mKeyguardUpdateMonitor.registerCallback(mInfoCallback); - mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive()); - mView.onThemeChanged(); + //mConfigurationController.addCallback(); + //mKeyguardUpdateMonitor.registerCallback(); } @Override protected void onViewDetached() { - mConfigurationController.removeCallback(mConfigurationListener); - mKeyguardUpdateMonitor.removeCallback(mInfoCallback); - } - - public void setMessage(CharSequence s) { - mView.setMessage(s); - } - - public void setMessage(int resId) { - mView.setMessage(resId); - } - - public void setNextMessageColor(ColorStateList colorState) { - mView.setNextMessageColor(colorState); + //mConfigurationController.removeCallback(); + //mKeyguardUpdateMonitor.removeCallback(); } - /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */ + /** Factory for createing {@link com.android.keyguard.KeyguardMessageAreaController}. */ public static class Factory { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 580d7043a220..12ea1d586e10 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -24,6 +24,7 @@ import android.view.animation.AnimationUtils; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; +import com.android.systemui.Dependency; import com.android.systemui.R; /** @@ -39,8 +40,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { private ViewGroup mRow1; private ViewGroup mRow2; private ViewGroup mRow3; + private View mDivider; private int mDisappearYTranslation; private View[][] mViews; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardPINView(Context context) { this(context, null); @@ -60,10 +63,15 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mContext, android.R.interpolator.fast_out_linear_in)); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); + mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } @Override protected void resetState() { + super.resetState(); + if (mSecurityMessageDisplay != null) { + mSecurityMessageDisplay.setMessage(""); + } } @Override @@ -80,6 +88,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mRow1 = findViewById(R.id.row1); mRow2 = findViewById(R.id.row2); mRow3 = findViewById(R.id.row3); + mDivider = findViewById(R.id.divider); mViews = new View[][]{ new View[]{ mRow0, null, null @@ -103,6 +112,18 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { new View[]{ null, mEcaView, null }}; + + View cancelBtn = findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + mCallback.reset(); + mCallback.onCancelClicked(); + }); + } + } + + @Override + public void showUsabilityHint() { } @Override @@ -126,21 +147,24 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { }); } - public boolean startDisappearAnimation(boolean needsSlowUnlockTransition, - final Runnable finishRunnable) { - + @Override + public boolean startDisappearAnimation(final Runnable finishRunnable) { enableClipping(false); setTranslationY(0); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */, mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator()); - DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition + DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor + .needsSlowUnlockTransition() ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mViews, - () -> { - enableClipping(true); - if (finishRunnable != null) { - finishRunnable.run(); + new Runnable() { + @Override + public void run() { + enableClipping(true); + if (finishRunnable != null) { + finishRunnable.run(); + } } }); return true; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index aaa5efec807e..97317cf5580f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -16,37 +16,50 @@ package com.android.keyguard; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; - import android.content.Context; import android.graphics.Rect; +import android.os.UserHandle; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.TextKeyListener; import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.TextViewInputDisabler; import com.android.systemui.R; + +import java.util.List; /** * Displays an alphanumeric (latin-1) key entry for the user to enter * an unlock password */ -public class KeyguardPasswordView extends KeyguardAbsKeyInputView { +public class KeyguardPasswordView extends KeyguardAbsKeyInputView + implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { + private final boolean mShowImeAtScreenOn; private final int mDisappearYTranslation; // A delay constant to be used in a workaround for the situation where InputMethodManagerService // is not switched to the new user yet. // TODO: Remove this by ensuring such a race condition never happens. + private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms + InputMethodManager mImm; private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; + private View mSwitchImeButton; private Interpolator mLinearOutSlowInInterpolator; private Interpolator mFastOutLinearInInterpolator; @@ -57,6 +70,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); + mShowImeAtScreenOn = context.getResources(). + getBoolean(R.bool.kg_show_ime_at_screen_on); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( @@ -67,6 +82,20 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { @Override protected void resetState() { + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + if (mSecurityMessageDisplay != null) { + mSecurityMessageDisplay.setMessage(""); + } + final boolean wasDisabled = mPasswordEntry.isEnabled(); + setPasswordEntryEnabled(true); + setPasswordEntryInputEnabled(true); + // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage. + if (!mResumed || !mPasswordEntry.isVisibleToUser()) { + return; + } + if (wasDisabled) { + mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); + } } @Override @@ -75,6 +104,29 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { } @Override + public boolean needsInput() { + return true; + } + + @Override + public void onResume(final int reason) { + super.onResume(reason); + + // Wait a bit to focus the field so the focusable flag on the window is already set then. + post(new Runnable() { + @Override + public void run() { + if (isShown() && mPasswordEntry.isEnabled()) { + mPasswordEntry.requestFocus(); + if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { + mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); + } + } + } + }); + } + + @Override protected int getPromptReasonStringRes(int reason) { switch (reason) { case PROMPT_REASON_RESTART: @@ -94,13 +146,97 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { } } + @Override + public void onPause() { + super.onPause(); + mImm.hideSoftInputFromWindow(getWindowToken(), 0); + } + + @Override + public void onStartingToHide() { + mImm.hideSoftInputFromWindow(getWindowToken(), 0); + } + + private void updateSwitchImeButton() { + // If there's more than one IME, enable the IME switcher button + final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE; + final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false); + if (wasVisible != shouldBeVisible) { + mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE); + } + + // TODO: Check if we still need this hack. + // If no icon is visible, reset the start margin on the password field so the text is + // still centered. + if (mSwitchImeButton.getVisibility() != View.VISIBLE) { + android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + final MarginLayoutParams mlp = (MarginLayoutParams) params; + mlp.setMarginStart(0); + mPasswordEntry.setLayoutParams(params); + } + } + } @Override protected void onFinishInflate() { super.onFinishInflate(); + mImm = (InputMethodManager) getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + mPasswordEntry = findViewById(getPasswordTextViewId()); + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); + mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordEntry.setOnEditorActionListener(this); + mPasswordEntry.addTextChangedListener(this); + + // Poke the wakelock any time the text is selected or modified + mPasswordEntry.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mCallback.userActivity(); + } + }); + + // Set selected property on so the view can send accessibility events. + mPasswordEntry.setSelected(true); + + mSwitchImeButton = findViewById(R.id.switch_ime_button); + mSwitchImeButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mCallback.userActivity(); // Leave the screen on a bit longer + // Do not show auxiliary subtypes in password lock screen. + mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */, + getContext().getDisplayId()); + } + }); + + View cancelBtn = findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + mCallback.reset(); + mCallback.onCancelClicked(); + }); + } + + // If there's more than one IME, enable the IME switcher button + updateSwitchImeButton(); + + // When we the current user is switching, InputMethodManagerService sometimes has not + // switched internal state yet here. As a quick workaround, we check the keyboard state + // again. + // TODO: Remove this workaround by ensuring such a race condition never happens. + postDelayed(new Runnable() { + @Override + public void run() { + updateSwitchImeButton(); + } + }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); } @Override @@ -129,6 +265,59 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { mPasswordEntryDisabler.setInputEnabled(enabled); } + /** + * Method adapted from com.android.inputmethod.latin.Utils + * + * @param imm The input method manager + * @param shouldIncludeAuxiliarySubtypes + * @return true if we have multiple IMEs to choose from + */ + private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, + final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> enabledImis = + imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); + + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : enabledImis) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List<InputMethodSubtype> subtypes = + imm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + return filteredImisCount > 1 + // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled + // input method subtype (The current IME should be LatinIME.) + || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; + } + + @Override + public void showUsabilityHint() { + } + @Override public int getWrongPasswordStringId() { return R.string.kg_wrong_password; @@ -157,8 +346,45 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { } @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + if (mCallback != null) { + mCallback.userActivity(); + } + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + // Poor man's user edit detection, assuming empty text is programmatic and everything else + // is from the user. + if (!TextUtils.isEmpty(s)) { + onUserInput(); + } + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + if (isSoftImeEvent || isKeyboardEnterKey) { + verifyPasswordAndUnlock(); + return true; + } + return false; + } + + @Override public CharSequence getTitle() { - return getResources().getString( + return getContext().getString( com.android.internal.R.string.keyguard_accessibility_password_unlock); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java deleted file mode 100644 index d34ea8c5e018..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.content.res.Resources; -import android.os.UserHandle; -import android.text.Editable; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.TextKeyListener; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup.MarginLayoutParams; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import java.util.List; - -public class KeyguardPasswordViewController - extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { - - private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms - - private final KeyguardSecurityCallback mKeyguardSecurityCallback; - private final InputMethodManager mInputMethodManager; - private final DelayableExecutor mMainExecutor; - private final boolean mShowImeAtScreenOn; - private TextView mPasswordEntry; - private View mSwitchImeButton; - - private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> { - // Check if this was the result of hitting the enter key - final boolean isSoftImeEvent = event == null - && (actionId == EditorInfo.IME_NULL - || actionId == EditorInfo.IME_ACTION_DONE - || actionId == EditorInfo.IME_ACTION_NEXT); - final boolean isKeyboardEnterKey = event != null - && KeyEvent.isConfirmKey(event.getKeyCode()) - && event.getAction() == KeyEvent.ACTION_DOWN; - if (isSoftImeEvent || isKeyboardEnterKey) { - verifyPasswordAndUnlock(); - return true; - } - return false; - }; - - private final TextWatcher mTextWatcher = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - mKeyguardSecurityCallback.userActivity(); - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - if (!TextUtils.isEmpty(s)) { - onUserInput(); - } - } - }; - - protected KeyguardPasswordViewController(KeyguardPasswordView view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, - LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, - InputMethodManager inputMethodManager, - @Main DelayableExecutor mainExecutor, - @Main Resources resources) { - super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker); - mKeyguardSecurityCallback = keyguardSecurityCallback; - mInputMethodManager = inputMethodManager; - mMainExecutor = mainExecutor; - mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); - mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); - mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); - } - - @Override - protected void onViewAttached() { - super.onViewAttached(); - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); - mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); - mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD); - - // Set selected property on so the view can send accessibility events. - mPasswordEntry.setSelected(true); - mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener); - mPasswordEntry.addTextChangedListener(mTextWatcher); - // Poke the wakelock any time the text is selected or modified - mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity()); - - mSwitchImeButton.setOnClickListener(v -> { - mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer - // Do not show auxiliary subtypes in password lock screen. - mInputMethodManager.showInputMethodPickerFromSystem(false, - mView.getContext().getDisplayId()); - }); - - View cancelBtn = mView.findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - mKeyguardSecurityCallback.reset(); - mKeyguardSecurityCallback.onCancelClicked(); - }); - } - - // If there's more than one IME, enable the IME switcher button - updateSwitchImeButton(); - - // When we the current user is switching, InputMethodManagerService sometimes has not - // switched internal state yet here. As a quick workaround, we check the keyboard state - // again. - // TODO: Remove this workaround by ensuring such a race condition never happens. - mMainExecutor.executeDelayed( - this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); - } - - @Override - protected void onViewDetached() { - super.onViewDetached(); - mPasswordEntry.setOnEditorActionListener(null); - } - - @Override - public boolean needsInput() { - return true; - } - - @Override - void resetState() { - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); - mMessageAreaController.setMessage(""); - final boolean wasDisabled = mPasswordEntry.isEnabled(); - mView.setPasswordEntryEnabled(true); - mView.setPasswordEntryInputEnabled(true); - // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage. - if (!mResumed || !mPasswordEntry.isVisibleToUser()) { - return; - } - if (wasDisabled) { - mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); - } - } - - @Override - public void onResume(int reason) { - super.onResume(reason); - // Wait a bit to focus the field so the focusable flag on the window is already set then. - mMainExecutor.execute(() -> { - if (mView.isShown() && mPasswordEntry.isEnabled()) { - mPasswordEntry.requestFocus(); - if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { - mInputMethodManager.showSoftInput( - mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); - } - } - }); - } - - @Override - public void onPause() { - super.onPause(); - mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0); - } - - @Override - public void onStartingToHide() { - mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0); - } - - private void updateSwitchImeButton() { - // If there's more than one IME, enable the IME switcher button - final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE; - final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes( - mInputMethodManager, false); - if (wasVisible != shouldBeVisible) { - mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE); - } - - // TODO: Check if we still need this hack. - // If no icon is visible, reset the start margin on the password field so the text is - // still centered. - if (mSwitchImeButton.getVisibility() != View.VISIBLE) { - android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); - if (params instanceof MarginLayoutParams) { - final MarginLayoutParams mlp = (MarginLayoutParams) params; - mlp.setMarginStart(0); - mPasswordEntry.setLayoutParams(params); - } - } - } - - /** - * Method adapted from com.android.inputmethod.latin.Utils - * - * @param imm The input method manager - * @param shouldIncludeAuxiliarySubtypes - * @return true if we have multiple IMEs to choose from - */ - private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, - final boolean shouldIncludeAuxiliarySubtypes) { - final List<InputMethodInfo> enabledImis = - imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); - - // Number of the filtered IMEs - int filteredImisCount = 0; - - for (InputMethodInfo imi : enabledImis) { - // We can return true immediately after we find two or more filtered IMEs. - if (filteredImisCount > 1) return true; - final List<InputMethodSubtype> subtypes = - imm.getEnabledInputMethodSubtypeList(imi, true); - // IMEs that have no subtypes should be counted. - if (subtypes.isEmpty()) { - ++filteredImisCount; - continue; - } - - int auxCount = 0; - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isAuxiliary()) { - ++auxCount; - } - } - final int nonAuxCount = subtypes.size() - auxCount; - - // IMEs that have one or more non-auxiliary subtypes should be counted. - // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary - // subtypes should be counted as well. - if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { - ++filteredImisCount; - continue; - } - } - - return filteredImisCount > 1 - // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's - //enabled input method subtype (The current IME should be LatinIME.) - || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index bdcf467c2456..c4a9fcb45284 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -15,39 +15,62 @@ */ package com.android.keyguard; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; + import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Rect; +import android.os.AsyncTask; +import android.os.CountDownTimer; import android.os.SystemClock; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.widget.LinearLayout; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; +import com.android.internal.widget.LockscreenCredential; import com.android.settingslib.animation.AppearAnimationCreator; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; +import com.android.systemui.Dependency; import com.android.systemui.R; -public class KeyguardPatternView extends KeyguardInputView - implements AppearAnimationCreator<LockPatternView.CellState> { +import java.util.List; + +public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, + AppearAnimationCreator<LockPatternView.CellState>, + EmergencyButton.EmergencyButtonCallback { private static final String TAG = "SecurityPatternView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; + // how long before we clear the wrong pattern + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; + // how many cells the user has to cross before we poke the wakelock + private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; + // How much we scale up the duration of the disappear animation when the current user is locked public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f; // Extra padding, in pixels, that should eat touch events. private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final AppearAnimationUtils mAppearAnimationUtils; private final DisappearAnimationUtils mDisappearAnimationUtils; private final DisappearAnimationUtils mDisappearAnimationUtilsLocked; @@ -55,7 +78,11 @@ public class KeyguardPatternView extends KeyguardInputView private final Rect mTempRect = new Rect(); private final Rect mLockPatternScreenBounds = new Rect(); + private CountDownTimer mCountdownTimer = null; + private LockPatternUtils mLockPatternUtils; + private AsyncTask<?, ?, ?> mPendingLockCheck; private LockPatternView mLockPatternView; + private KeyguardSecurityCallback mCallback; /** * Keeps track of the last time we poked the wake lock during dispatching of the touch event. @@ -65,9 +92,26 @@ public class KeyguardPatternView extends KeyguardInputView */ private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; + /** + * Useful for clearing out the wrong pattern after a delay + */ + private Runnable mCancelPatternRunnable = new Runnable() { + @Override + public void run() { + mLockPatternView.clearPattern(); + } + }; + @VisibleForTesting KeyguardMessageArea mSecurityMessageDisplay; private View mEcaView; private ViewGroup mContainer; + private int mDisappearYTranslation; + + enum FooterMode { + Normal, + ForgotLockPattern, + VerifyUnlocked + } public KeyguardPatternView(Context context) { this(context, null); @@ -75,6 +119,7 @@ public class KeyguardPatternView extends KeyguardInputView public KeyguardPatternView(Context context, AttributeSet attrs) { super(context, attrs); + mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); mAppearAnimationUtils = new AppearAnimationUtils(context, AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, 2.0f /* delayScale */, AnimationUtils.loadInterpolator( @@ -87,16 +132,50 @@ public class KeyguardPatternView extends KeyguardInputView (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */, 0.6f /* delayScale */, AnimationUtils.loadInterpolator( mContext, android.R.interpolator.fast_out_linear_in)); + mDisappearYTranslation = getResources().getDimensionPixelSize( + R.dimen.disappear_y_translation); + } + + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; } @Override protected void onFinishInflate() { super.onFinishInflate(); + mLockPatternUtils = mLockPatternUtils == null + ? new LockPatternUtils(mContext) : mLockPatternUtils; mLockPatternView = findViewById(R.id.lockPatternView); + mLockPatternView.setSaveEnabled(false); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); + + // vibrate mode will be the same for the life of this screen + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); mEcaView = findViewById(R.id.keyguard_selector_fade_container); mContainer = findViewById(R.id.container); + + EmergencyButton button = findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(this); + } + + View cancelBtn = findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + mCallback.reset(); + mCallback.onCancelClicked(); + }); + } } @Override @@ -106,6 +185,11 @@ public class KeyguardPatternView extends KeyguardInputView } @Override + public void onEmergencyButtonClickedWhenInCall() { + mCallback.reset(); + } + + @Override public boolean onTouchEvent(MotionEvent ev) { boolean result = super.onTouchEvent(ev); // as long as the user is entering a pattern (i.e sending a touch event that was handled @@ -133,11 +217,248 @@ public class KeyguardPatternView extends KeyguardInputView } @Override - boolean disallowInterceptTouch(MotionEvent event) { + public void reset() { + // reset lock pattern + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); + mLockPatternView.enableInput(); + mLockPatternView.setEnabled(true); + mLockPatternView.clearPattern(); + + if (mSecurityMessageDisplay == null) { + return; + } + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser()); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + displayDefaultSecurityMessage(); + } + } + + private void displayDefaultSecurityMessage() { + if (mSecurityMessageDisplay != null) { + mSecurityMessageDisplay.setMessage(""); + } + } + + @Override + public void showUsabilityHint() { + } + + @Override + public boolean disallowInterceptTouch(MotionEvent event) { return !mLockPatternView.isEmpty() || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY()); } + /** TODO: hook this up */ + public void cleanUp() { + if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); + mLockPatternUtils = null; + mLockPatternView.setOnPatternListener(null); + } + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + @Override + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + mSecurityMessageDisplay.setMessage(""); + } + + @Override + public void onPatternCleared() { + } + + @Override + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { + mCallback.userActivity(); + mCallback.onUserInput(); + } + + @Override + public void onPatternDetected(final List<LockPatternView.Cell> pattern) { + mKeyguardUpdateMonitor.setCredentialAttempted(); + mLockPatternView.disableInput(); + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + final int userId = KeyguardUpdateMonitor.getCurrentUser(); + if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + mLockPatternView.enableInput(); + onPatternChecked(userId, false, 0, false /* not valid - too short */); + return; + } + + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); + LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + mPendingLockCheck = LockPatternChecker.checkCredential( + mLockPatternUtils, + LockscreenCredential.createPattern(pattern), + userId, + new LockPatternChecker.OnCheckCallback() { + + @Override + public void onEarlyMatched() { + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL); + } + onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, + true /* isValidPattern */); + } + + @Override + public void onChecked(boolean matched, int timeoutMs) { + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + mLockPatternView.enableInput(); + mPendingLockCheck = null; + if (!matched) { + onPatternChecked(userId, false /* matched */, timeoutMs, + true /* isValidPattern */); + } + } + + @Override + public void onCancelled() { + // We already got dismissed with the early matched callback, so we + // cancelled the check. However, we still need to note down the latency. + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + } + }); + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.userActivity(); + mCallback.onUserInput(); + } + } + + private void onPatternChecked(int userId, boolean matched, int timeoutMs, + boolean isValidPattern) { + boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + if (matched) { + mCallback.reportUnlockAttempt(userId, true, 0); + if (dismissKeyguard) { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); + mCallback.dismiss(true, userId); + } + } else { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + if (isValidPattern) { + mCallback.reportUnlockAttempt(userId, false, timeoutMs); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + userId, timeoutMs); + handleAttemptLockout(deadline); + } + } + if (timeoutMs == 0) { + mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + } + } + } + } + + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mLockPatternView.clearPattern(); + mLockPatternView.setEnabled(false); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); + mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); + } + + @Override + public void onFinish() { + mLockPatternView.setEnabled(true); + displayDefaultSecurityMessage(); + } + + }.start(); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + mPendingLockCheck = null; + } + displayDefaultSecurityMessage(); + } + + @Override + public void onResume(int reason) { + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + + @Override + public void showPromptReason(int reason) { + switch (reason) { + case PROMPT_REASON_RESTART: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern); + break; + case PROMPT_REASON_TIMEOUT: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + case PROMPT_REASON_DEVICE_ADMIN: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin); + break; + case PROMPT_REASON_USER_REQUEST: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request); + break; + case PROMPT_REASON_PREPARE_FOR_UPDATE: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + case PROMPT_REASON_NONE: + break; + default: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + } + } + + @Override + public void showMessage(CharSequence message, ColorStateList colorState) { + if (colorState != null) { + mSecurityMessageDisplay.setNextMessageColor(colorState); + } + mSecurityMessageDisplay.setMessage(message); + } + + @Override public void startAppearAnimation() { enableClipping(false); setAlpha(1f); @@ -146,7 +467,12 @@ public class KeyguardPatternView extends KeyguardInputView 0, mAppearAnimationUtils.getInterpolator()); mAppearAnimationUtils.startAnimation2d( mLockPatternView.getCellStates(), - () -> enableClipping(true), + new Runnable() { + @Override + public void run() { + enableClipping(true); + } + }, this); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, @@ -158,9 +484,11 @@ public class KeyguardPatternView extends KeyguardInputView } } - public boolean startDisappearAnimation(boolean needsSlowUnlockTransition, - final Runnable finishRunnable) { - float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f; + @Override + public boolean startDisappearAnimation(final Runnable finishRunnable) { + float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition() + ? DISAPPEAR_MULTIPLIER_LOCKED + : 1f; mLockPatternView.clearPattern(); enableClipping(false); setTranslationY(0); @@ -169,8 +497,10 @@ public class KeyguardPatternView extends KeyguardInputView -mDisappearAnimationUtils.getStartTranslation(), mDisappearAnimationUtils.getInterpolator()); - DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition - ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; + DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor + .needsSlowUnlockTransition() + ? mDisappearAnimationUtilsLocked + : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), () -> { enableClipping(true); @@ -219,7 +549,7 @@ public class KeyguardPatternView extends KeyguardInputView @Override public CharSequence getTitle() { - return getResources().getString( + return getContext().getString( com.android.internal.R.string.keyguard_accessibility_pattern_unlock); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java deleted file mode 100644 index 3db9db7be00c..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; - -import android.content.res.ColorStateList; -import android.os.AsyncTask; -import android.os.CountDownTimer; -import android.os.SystemClock; -import android.view.View; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternChecker; -import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.LockPatternView; -import com.android.internal.widget.LockPatternView.Cell; -import com.android.internal.widget.LockscreenCredential; -import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; - -import java.util.List; - -public class KeyguardPatternViewController - extends KeyguardInputViewController<KeyguardPatternView> { - - // how many cells the user has to cross before we poke the wakelock - private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; - - // how long before we clear the wrong pattern - private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; - - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final LockPatternUtils mLockPatternUtils; - private final LatencyTracker mLatencyTracker; - private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; - - private KeyguardMessageAreaController mMessageAreaController; - private LockPatternView mLockPatternView; - private CountDownTimer mCountdownTimer; - private AsyncTask<?, ?, ?> mPendingLockCheck; - - private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { - @Override - public void onEmergencyButtonClickedWhenInCall() { - getKeyguardSecurityCallback().reset(); - } - }; - - /** - * Useful for clearing out the wrong pattern after a delay - */ - private Runnable mCancelPatternRunnable = new Runnable() { - @Override - public void run() { - mLockPatternView.clearPattern(); - } - }; - - private class UnlockPatternListener implements LockPatternView.OnPatternListener { - - @Override - public void onPatternStart() { - mLockPatternView.removeCallbacks(mCancelPatternRunnable); - mMessageAreaController.setMessage(""); - } - - @Override - public void onPatternCleared() { - } - - @Override - public void onPatternCellAdded(List<Cell> pattern) { - getKeyguardSecurityCallback().userActivity(); - getKeyguardSecurityCallback().onUserInput(); - } - - @Override - public void onPatternDetected(final List<LockPatternView.Cell> pattern) { - mKeyguardUpdateMonitor.setCredentialAttempted(); - mLockPatternView.disableInput(); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - final int userId = KeyguardUpdateMonitor.getCurrentUser(); - if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { - mLockPatternView.enableInput(); - onPatternChecked(userId, false, 0, false /* not valid - too short */); - return; - } - - mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL); - mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); - mPendingLockCheck = LockPatternChecker.checkCredential( - mLockPatternUtils, - LockscreenCredential.createPattern(pattern), - userId, - new LockPatternChecker.OnCheckCallback() { - - @Override - public void onEarlyMatched() { - mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL); - onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, - true /* isValidPattern */); - } - - @Override - public void onChecked(boolean matched, int timeoutMs) { - mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); - mLockPatternView.enableInput(); - mPendingLockCheck = null; - if (!matched) { - onPatternChecked(userId, false /* matched */, timeoutMs, - true /* isValidPattern */); - } - } - - @Override - public void onCancelled() { - // We already got dismissed with the early matched callback, so we - // cancelled the check. However, we still need to note down the latency. - mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - }); - if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { - getKeyguardSecurityCallback().userActivity(); - getKeyguardSecurityCallback().onUserInput(); - } - } - - private void onPatternChecked(int userId, boolean matched, int timeoutMs, - boolean isValidPattern) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; - if (matched) { - getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); - if (dismissKeyguard) { - mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); - getKeyguardSecurityCallback().dismiss(true, userId); - } - } else { - mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); - if (isValidPattern) { - getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); - if (timeoutMs > 0) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - userId, timeoutMs); - handleAttemptLockout(deadline); - } - } - if (timeoutMs == 0) { - mMessageAreaController.setMessage(R.string.kg_wrong_pattern); - mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); - } - } - } - } - - protected KeyguardPatternViewController(KeyguardPatternView view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, - LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - LatencyTracker latencyTracker, - KeyguardMessageAreaController.Factory messageAreaControllerFactory) { - super(view, securityMode, keyguardSecurityCallback); - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mLockPatternUtils = lockPatternUtils; - mLatencyTracker = latencyTracker; - mMessageAreaControllerFactory = messageAreaControllerFactory; - KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); - mMessageAreaController = mMessageAreaControllerFactory.create(kma); - mLockPatternView = mView.findViewById(R.id.lockPatternView); - } - - @Override - public void init() { - super.init(); - mMessageAreaController.init(); - } - - @Override - protected void onViewAttached() { - mLockPatternView.setOnPatternListener(new UnlockPatternListener()); - mLockPatternView.setSaveEnabled(false); - mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( - KeyguardUpdateMonitor.getCurrentUser())); - // vibrate mode will be the same for the life of this screen - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); - - EmergencyButton button = mView.findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(mEmergencyButtonCallback); - } - - View cancelBtn = mView.findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - getKeyguardSecurityCallback().reset(); - getKeyguardSecurityCallback().onCancelClicked(); - }); - } - } - - @Override - protected void onViewDetached() { - super.onViewDetached(); - mLockPatternView.setOnPatternListener(null); - EmergencyButton button = mView.findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(null); - } - View cancelBtn = mView.findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(null); - } - } - - @Override - public void reset() { - // reset lock pattern - mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( - KeyguardUpdateMonitor.getCurrentUser())); - mLockPatternView.enableInput(); - mLockPatternView.setEnabled(true); - mLockPatternView.clearPattern(); - - // if the user is currently locked out, enforce it. - long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); - if (deadline != 0) { - handleAttemptLockout(deadline); - } else { - displayDefaultSecurityMessage(); - } - } - - @Override - public void onPause() { - super.onPause(); - - if (mCountdownTimer != null) { - mCountdownTimer.cancel(); - mCountdownTimer = null; - } - - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; - } - displayDefaultSecurityMessage(); - } - - @Override - public boolean needsInput() { - return false; - } - - @Override - public void showPromptReason(int reason) { - /// TODO: move all this logic into the MessageAreaController? - switch (reason) { - case PROMPT_REASON_RESTART: - mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern); - break; - case PROMPT_REASON_TIMEOUT: - mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); - break; - case PROMPT_REASON_DEVICE_ADMIN: - mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin); - break; - case PROMPT_REASON_USER_REQUEST: - mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request); - break; - case PROMPT_REASON_PREPARE_FOR_UPDATE: - mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); - break; - case PROMPT_REASON_NONE: - break; - default: - mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); - break; - } - } - - @Override - public void showMessage(CharSequence message, ColorStateList colorState) { - if (colorState != null) { - mMessageAreaController.setNextMessageColor(colorState); - } - mMessageAreaController.setMessage(message); - } - - @Override - public void startAppearAnimation() { - super.startAppearAnimation(); - } - - @Override - public boolean startDisappearAnimation(Runnable finishRunnable) { - return mView.startDisappearAnimation( - mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); - } - - private void displayDefaultSecurityMessage() { - mMessageAreaController.setMessage(""); - } - - private void handleAttemptLockout(long elapsedRealtimeDeadline) { - mLockPatternView.clearPattern(); - mLockPatternView.setEnabled(false); - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long secondsInFuture = (long) Math.ceil( - (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); - mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { - - @Override - public void onTick(long millisUntilFinished) { - final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mMessageAreaController.setMessage(mView.getResources().getQuantityString( - R.plurals.kg_too_many_failed_attempts_countdown, - secondsRemaining, secondsRemaining)); - } - - @Override - public void onFinish() { - mLockPatternView.setEnabled(true); - displayDefaultSecurityMessage(); - } - - }.start(); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 7fa43116a7b1..c7f27cf8a71a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -16,17 +16,11 @@ package com.android.keyguard; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; -import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; - import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import com.android.internal.widget.LockscreenCredential; @@ -35,12 +29,22 @@ import com.android.systemui.R; /** * A Pin based Keyguard input view */ -public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView { +public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView + implements View.OnKeyListener, View.OnTouchListener { protected PasswordTextView mPasswordEntry; private View mOkButton; private View mDeleteButton; - private View[] mButtons = new View[10]; + private View mButton0; + private View mButton1; + private View mButton2; + private View mButton3; + private View mButton4; + private View mButton5; + private View mButton6; + private View mButton7; + private View mButton8; + private View mButton9; public KeyguardPinBasedInputView(Context context) { this(context, null); @@ -58,6 +62,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override protected void resetState() { + setPasswordEntryEnabled(true); } @Override @@ -81,10 +86,10 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (KeyEvent.isConfirmKey(keyCode)) { - mOkButton.performClick(); + performClick(mOkButton); return true; } else if (keyCode == KeyEvent.KEYCODE_DEL) { - mDeleteButton.performClick(); + performClick(mDeleteButton); return true; } if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { @@ -120,9 +125,42 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } } + private void performClick(View view) { + view.performClick(); + } + private void performNumberClick(int number) { - if (number >= 0 && number <= 9) { - mButtons[number].performClick(); + switch (number) { + case 0: + performClick(mButton0); + break; + case 1: + performClick(mButton1); + break; + case 2: + performClick(mButton2); + break; + case 3: + performClick(mButton3); + break; + case 4: + performClick(mButton4); + break; + case 5: + performClick(mButton5); + break; + case 6: + performClick(mButton6); + break; + case 7: + performClick(mButton7); + break; + case 8: + performClick(mButton8); + break; + case 9: + performClick(mButton9); + break; } } @@ -139,31 +177,94 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override protected void onFinishInflate() { mPasswordEntry = findViewById(getPasswordTextViewId()); + mPasswordEntry.setOnKeyListener(this); // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); + mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() { + @Override + public void onUserActivity() { + onUserInput(); + } + }); + mOkButton = findViewById(R.id.key_enter); + if (mOkButton != null) { + mOkButton.setOnTouchListener(this); + mOkButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mPasswordEntry.isEnabled()) { + verifyPasswordAndUnlock(); + } + } + }); + mOkButton.setOnHoverListener(new LiftToActivateListener(getContext())); + } mDeleteButton = findViewById(R.id.delete_button); mDeleteButton.setVisibility(View.VISIBLE); + mDeleteButton.setOnTouchListener(this); + mDeleteButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // check for time-based lockouts + if (mPasswordEntry.isEnabled()) { + mPasswordEntry.deleteLastChar(); + } + } + }); + mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + // check for time-based lockouts + if (mPasswordEntry.isEnabled()) { + resetPasswordText(true /* animate */, true /* announce */); + } + doHapticKeyClick(); + return true; + } + }); - mButtons[0] = findViewById(R.id.key0); - mButtons[1] = findViewById(R.id.key1); - mButtons[2] = findViewById(R.id.key2); - mButtons[3] = findViewById(R.id.key3); - mButtons[4] = findViewById(R.id.key4); - mButtons[5] = findViewById(R.id.key5); - mButtons[6] = findViewById(R.id.key6); - mButtons[7] = findViewById(R.id.key7); - mButtons[8] = findViewById(R.id.key8); - mButtons[9] = findViewById(R.id.key9); + mButton0 = findViewById(R.id.key0); + mButton1 = findViewById(R.id.key1); + mButton2 = findViewById(R.id.key2); + mButton3 = findViewById(R.id.key3); + mButton4 = findViewById(R.id.key4); + mButton5 = findViewById(R.id.key5); + mButton6 = findViewById(R.id.key6); + mButton7 = findViewById(R.id.key7); + mButton8 = findViewById(R.id.key8); + mButton9 = findViewById(R.id.key9); mPasswordEntry.requestFocus(); super.onFinishInflate(); } @Override + public void onResume(int reason) { + super.onResume(reason); + mPasswordEntry.requestFocus(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + doHapticKeyClick(); + } + return false; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + return onKeyDown(keyCode, event); + } + return false; + } + + @Override public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_pin_unlock); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java deleted file mode 100644 index 4d0ebfffbe04..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnKeyListener; -import android.view.View.OnTouchListener; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; - -public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> - extends KeyguardAbsKeyInputViewController<T> { - - private final LiftToActivateListener mLiftToActivateListener; - protected PasswordTextView mPasswordEntry; - - private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - return mView.onKeyDown(keyCode, event); - } - return false; - }; - - private final OnTouchListener mOnTouchListener = (v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mView.doHapticKeyClick(); - } - return false; - }; - - protected KeyguardPinBasedInputViewController(T view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, - LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, - LiftToActivateListener liftToActivateListener) { - super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker); - mLiftToActivateListener = liftToActivateListener; - mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); - } - - @Override - protected void onViewAttached() { - super.onViewAttached(); - - mPasswordEntry.setOnKeyListener(mOnKeyListener); - mPasswordEntry.setUserActivityListener(this::onUserInput); - - View deleteButton = mView.findViewById(R.id.delete_button); - deleteButton.setOnTouchListener(mOnTouchListener); - deleteButton.setOnClickListener(v -> { - // check for time-based lockouts - if (mPasswordEntry.isEnabled()) { - mPasswordEntry.deleteLastChar(); - } - }); - deleteButton.setOnLongClickListener(v -> { - // check for time-based lockouts - if (mPasswordEntry.isEnabled()) { - mView.resetPasswordText(true /* animate */, true /* announce */); - } - mView.doHapticKeyClick(); - return true; - }); - - View okButton = mView.findViewById(R.id.key_enter); - if (okButton != null) { - okButton.setOnTouchListener(mOnTouchListener); - okButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPasswordEntry.isEnabled()) { - verifyPasswordAndUnlock(); - } - } - }); - okButton.setOnHoverListener(mLiftToActivateListener); - } - } - - @Override - public void onResume(int reason) { - super.onResume(reason); - mPasswordEntry.requestFocus(); - } - - @Override - void resetState() { - mView.setPasswordEntryEnabled(true); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java deleted file mode 100644 index 6769436be8ef..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.view.View; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; - -public class KeyguardPinViewController - extends KeyguardPinBasedInputViewController<KeyguardPINView> { - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - - protected KeyguardPinViewController(KeyguardPINView view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, - LiftToActivateListener liftToActivateListener) { - super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, liftToActivateListener); - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - } - - @Override - protected void onViewAttached() { - super.onViewAttached(); - - View cancelBtn = mView.findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - getKeyguardSecurityCallback().reset(); - getKeyguardSecurityCallback().onCancelClicked(); - }); - } - } - - @Override - void resetState() { - super.resetState(); - mMessageAreaController.setMessage(""); - } - - @Override - public boolean startDisappearAnimation(Runnable finishRunnable) { - return mView.startDisappearAnimation( - mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index b62ea6bc2ff6..81d37a830f8f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -19,6 +19,8 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; +import static com.android.systemui.DejankUtils.whitelistIpcs; + import static java.lang.Integer.max; import android.animation.Animator; @@ -26,14 +28,25 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; +import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.Intent; +import android.content.res.ColorStateList; import android.graphics.Insets; import android.graphics.Rect; +import android.metrics.LogMaker; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; +import android.util.Slog; import android.util.TypedValue; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; +import android.view.View; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; @@ -48,30 +61,42 @@ import androidx.annotation.VisibleForTesting; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; +import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; +import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.InjectionInflationController; import java.util.List; -public class KeyguardSecurityContainer extends FrameLayout { - static final int USER_TYPE_PRIMARY = 1; - static final int USER_TYPE_WORK_PROFILE = 2; - static final int USER_TYPE_SECONDARY_USER = 3; +public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView { + private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final String TAG = "KeyguardSecurityView"; + + private static final int USER_TYPE_PRIMARY = 1; + private static final int USER_TYPE_WORK_PROFILE = 2; + private static final int USER_TYPE_SECONDARY_USER = 3; // Bouncer is dismissed due to no security. - static final int BOUNCER_DISMISS_NONE_SECURITY = 0; + private static final int BOUNCER_DISMISS_NONE_SECURITY = 0; // Bouncer is dismissed due to pin, password or pattern entered. - static final int BOUNCER_DISMISS_PASSWORD = 1; + private static final int BOUNCER_DISMISS_PASSWORD = 1; // Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated. - static final int BOUNCER_DISMISS_BIOMETRIC = 2; + private static final int BOUNCER_DISMISS_BIOMETRIC = 2; // Bouncer is dismissed due to extended access granted. - static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3; + private static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3; // Bouncer is dismissed due to sim card unlock code entered. - static final int BOUNCER_DISMISS_SIM = 4; + private static final int BOUNCER_DISMISS_SIM = 4; // Make the view move slower than the finger, as if the spring were applying force. private static final float TOUCH_Y_MULTIPLIER = 0.25f; @@ -80,23 +105,36 @@ public class KeyguardSecurityContainer extends FrameLayout { // How much to scale the default slop by, to avoid accidental drags. private static final float SLOP_SCALE = 4f; + private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl(); + private static final long IME_DISAPPEAR_DURATION_MS = 125; + private KeyguardSecurityModel mSecurityModel; + private LockPatternUtils mLockPatternUtils; + @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; + private boolean mIsVerifyUnlockOnly; + private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; + private KeyguardSecurityView mCurrentSecurityView; + private SecurityCallback mSecurityCallback; private AlertDialog mAlertDialog; + private InjectionInflationController mInjectionInflationController; private boolean mSwipeUpToRetry; + private AdminSecondaryLockScreenController mSecondaryLockScreenController; private final ViewConfiguration mViewConfiguration; private final SpringAnimation mSpringAnimation; private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardStateController mKeyguardStateController; + private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private float mLastTouchY = -1; private int mActivePointerId = -1; private boolean mIsDragging; private float mStartTouchY = -1; private boolean mDisappearAnimRunning; - private SwipeListener mSwipeListener; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -148,22 +186,19 @@ public class KeyguardSecurityContainer extends FrameLayout { // Used to notify the container when something interesting happens. public interface SecurityCallback { - boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen); - void userActivity(); - void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); + public boolean dismiss(boolean authenticated, int targetUserId, + boolean bypassSecondaryLockScreen); + public void userActivity(); + public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); /** * @param strongAuth wheher the user has authenticated with strong authentication like * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the finish completion. */ - void finish(boolean strongAuth, int targetUserId); - void reset(); - void onCancelClicked(); - } - - public interface SwipeListener { - void onSwipeUp(); + public void finish(boolean strongAuth, int targetUserId); + public void reset(); + public void onCancelClicked(); } @VisibleForTesting @@ -214,24 +249,52 @@ public class KeyguardSecurityContainer extends FrameLayout { public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mSecurityModel = Dependency.get(KeyguardSecurityModel.class); + mLockPatternUtils = new LockPatternUtils(context); + mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); + mInjectionInflationController = new InjectionInflationController( + SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory()); mViewConfiguration = ViewConfiguration.get(context); + mKeyguardStateController = Dependency.get(KeyguardStateController.class); + mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this, + mUpdateMonitor, mCallback, new Handler(Looper.myLooper())); + } + + public void setSecurityCallback(SecurityCallback callback) { + mSecurityCallback = callback; } - void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { + @Override + public void onResume(int reason) { + if (mCurrentSecuritySelection != SecurityMode.None) { + getSecurityView(mCurrentSecuritySelection).onResume(reason); + } mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); - updateBiometricRetry(securityMode, faceAuthEnabled); + updateBiometricRetry(); } + @Override public void onPause() { if (mAlertDialog != null) { mAlertDialog.dismiss(); mAlertDialog = null; } + mSecondaryLockScreenController.hide(); + if (mCurrentSecuritySelection != SecurityMode.None) { + getSecurityView(mCurrentSecuritySelection).onPause(); + } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); } @Override + public void onStartingToHide() { + if (mCurrentSecuritySelection != SecurityMode.None) { + getSecurityView(mCurrentSecuritySelection).onStartingToHide(); + } + } + + @Override public boolean shouldDelayChildPressedState() { return true; } @@ -253,12 +316,13 @@ public class KeyguardSecurityContainer extends FrameLayout { return false; } // Avoid dragging the pattern view - if (mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) { + if (mCurrentSecurityView.disallowInterceptTouch(event)) { return false; } int index = event.findPointerIndex(mActivePointerId); float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE; - if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) { + if (mCurrentSecurityView != null && index != -1 + && mStartTouchY - event.getY(index) > touchSlop) { mIsDragging = true; return true; } @@ -306,28 +370,31 @@ public class KeyguardSecurityContainer extends FrameLayout { } if (action == MotionEvent.ACTION_UP) { if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - MIN_DRAG_SIZE, getResources().getDisplayMetrics())) { - if (mSwipeListener != null) { - mSwipeListener.onSwipeUp(); - } + MIN_DRAG_SIZE, getResources().getDisplayMetrics()) + && !mUpdateMonitor.isFaceDetectionRunning()) { + mUpdateMonitor.requestFaceAuth(); + mCallback.userActivity(); + showMessage(null, null); } } return true; } - void setSwipeListener(SwipeListener swipeListener) { - mSwipeListener = swipeListener; - } - private void startSpringAnimation(float startVelocity) { mSpringAnimation .setStartVelocity(startVelocity) .animateToFinalPosition(0); } - public void startDisappearAnimation(SecurityMode securitySelection) { + public void startAppearAnimation() { + if (mCurrentSecuritySelection != SecurityMode.None) { + getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); + } + } + + public boolean startDisappearAnimation(Runnable onFinishRunnable) { mDisappearAnimRunning = true; - if (securitySelection == SecurityMode.Password) { + if (mCurrentSecuritySelection == SecurityMode.Password) { mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(), IME_DISAPPEAR_DURATION_MS, Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() { @@ -372,13 +439,19 @@ public class KeyguardSecurityContainer extends FrameLayout { } }); } + if (mCurrentSecuritySelection != SecurityMode.None) { + return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation( + onFinishRunnable); + } + return false; } /** * Enables/disables swipe up to retry on the bouncer. */ - private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) { - mSwipeUpToRetry = faceAuthEnabled + private void updateBiometricRetry() { + SecurityMode securityMode = getSecurityMode(); + mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled() && securityMode != SecurityMode.SimPin && securityMode != SecurityMode.SimPuk && securityMode != SecurityMode.None; @@ -388,11 +461,53 @@ public class KeyguardSecurityContainer extends FrameLayout { return mSecurityViewFlipper.getTitle(); } + @VisibleForTesting + protected KeyguardSecurityView getSecurityView(SecurityMode securityMode) { + final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); + KeyguardSecurityView view = null; + final int children = mSecurityViewFlipper.getChildCount(); + for (int child = 0; child < children; child++) { + if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) { + view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child)); + break; + } + } + int layoutId = getLayoutIdFor(securityMode); + if (view == null && layoutId != 0) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); + View v = mInjectionInflationController.injectable(inflater) + .inflate(layoutId, mSecurityViewFlipper, false); + mSecurityViewFlipper.addView(v); + updateSecurityView(v); + view = (KeyguardSecurityView)v; + view.reset(); + } + + return view; + } + + private void updateSecurityView(View view) { + if (view instanceof KeyguardSecurityView) { + KeyguardSecurityView ksv = (KeyguardSecurityView) view; + ksv.setKeyguardCallback(mCallback); + ksv.setLockPatternUtils(mLockPatternUtils); + } else { + Log.w(TAG, "View " + view + " is not a KeyguardSecurityView"); + } + } @Override public void onFinishInflate() { super.onFinishInflate(); mSecurityViewFlipper = findViewById(R.id.view_flipper); + mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + mSecurityModel.setLockPatternUtils(utils); + mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); } @Override @@ -424,12 +539,11 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog.show(); } - void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils, - SecurityMode securityMode) { - int timeoutInSeconds = timeoutMs / 1000; + private void showTimeoutDialog(int userId, int timeoutMs) { + int timeoutInSeconds = (int) timeoutMs / 1000; int messageId = 0; - switch (securityMode) { + switch (mSecurityModel.getSecurityMode(userId)) { case Pattern: messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message; break; @@ -449,13 +563,13 @@ public class KeyguardSecurityContainer extends FrameLayout { if (messageId != 0) { final String message = mContext.getString(messageId, - lockPatternUtils.getCurrentFailedPasswordAttempts(userId), + mLockPatternUtils.getCurrentFailedPasswordAttempts(userId), timeoutInSeconds); showDialog(null, message); } } - void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { + private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { case USER_TYPE_PRIMARY: @@ -474,7 +588,7 @@ public class KeyguardSecurityContainer extends FrameLayout { showDialog(null, message); } - void showWipeDialog(int attempts, int userType) { + private void showWipeDialog(int attempts, int userType) { String message = null; switch (userType) { case USER_TYPE_PRIMARY: @@ -493,8 +607,358 @@ public class KeyguardSecurityContainer extends FrameLayout { showDialog(null, message); } + private void reportFailedUnlockAttempt(int userId, int timeoutMs) { + // +1 for this time + final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1; + + if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); + + final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); + final int failedAttemptsBeforeWipe = + dpm.getMaximumFailedPasswordsForWipe(null, userId); + + final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? + (failedAttemptsBeforeWipe - failedAttempts) + : Integer.MAX_VALUE; // because DPM returns 0 if no restriction + if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { + // The user has installed a DevicePolicyManager that requests a user/profile to be wiped + // N attempts. Once we get below the grace period, we post this dialog every time as a + // clear warning until the deletion fires. + // Check which profile has the strictest policy for failed password attempts + final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); + int userType = USER_TYPE_PRIMARY; + if (expiringUser == userId) { + // TODO: http://b/23522538 + if (expiringUser != UserHandle.USER_SYSTEM) { + userType = USER_TYPE_SECONDARY_USER; + } + } else if (expiringUser != UserHandle.USER_NULL) { + userType = USER_TYPE_WORK_PROFILE; + } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY + if (remainingBeforeWipe > 0) { + showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); + } else { + // Too many attempts. The device will be wiped shortly. + Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); + showWipeDialog(failedAttempts, userType); + } + } + mLockPatternUtils.reportFailedPasswordAttempt(userId); + if (timeoutMs > 0) { + mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); + showTimeoutDialog(userId, timeoutMs); + } + } + + /** + * Shows the primary security screen for the user. This will be either the multi-selector + * or the user's security method. + * @param turningOff true if the device is being turned off + */ + void showPrimarySecurityScreen(boolean turningOff) { + SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser())); + if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); + showSecurityScreen(securityMode); + } + + /** + * Shows the next security screen if there is one. + * @param authenticated true if the user entered the correct authentication + * @param targetUserId a user that needs to be the foreground user at the finish (if called) + * completion. + * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary + * secondary lock screen requirement, if any. + * @return true if keyguard is done + */ + boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, + boolean bypassSecondaryLockScreen) { + if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); + boolean finish = false; + boolean strongAuth = false; + int eventSubtype = -1; + BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN; + if (mUpdateMonitor.getUserHasTrust(targetUserId)) { + finish = true; + eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS; + } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { + finish = true; + eventSubtype = BOUNCER_DISMISS_BIOMETRIC; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC; + } else if (SecurityMode.None == mCurrentSecuritySelection) { + SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); + if (SecurityMode.None == securityMode) { + finish = true; // no security required + eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY; + } else { + showSecurityScreen(securityMode); // switch to the alternate security view + } + } else if (authenticated) { + switch (mCurrentSecuritySelection) { + case Pattern: + case Password: + case PIN: + strongAuth = true; + finish = true; + eventSubtype = BOUNCER_DISMISS_PASSWORD; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD; + break; + + case SimPin: + case SimPuk: + // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home + SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); + if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser())) { + finish = true; + eventSubtype = BOUNCER_DISMISS_SIM; + uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; + } else { + showSecurityScreen(securityMode); + } + break; + + default: + Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); + showPrimarySecurityScreen(false); + break; + } + } + // Check for device admin specified additional security measures. + if (finish && !bypassSecondaryLockScreen) { + Intent secondaryLockscreenIntent = + mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); + if (secondaryLockscreenIntent != null) { + mSecondaryLockScreenController.show(secondaryLockscreenIntent); + return false; + } + } + if (eventSubtype != -1) { + mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) + .setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); + } + if (uiEvent != BouncerUiEvent.UNKNOWN) { + sUiEventLogger.log(uiEvent); + } + if (finish) { + mSecurityCallback.finish(strongAuth, targetUserId); + } + return finish; + } + + /** + * Switches to the given security view unless it's already being shown, in which case + * this is a no-op. + * + * @param securityMode + */ + private void showSecurityScreen(SecurityMode securityMode) { + if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); + + if (securityMode == mCurrentSecuritySelection) return; + + KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); + KeyguardSecurityView newView = getSecurityView(securityMode); + + // Emulate Activity life cycle + if (oldView != null) { + oldView.onPause(); + oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view + } + if (securityMode != SecurityMode.None) { + newView.onResume(KeyguardSecurityView.VIEW_REVEALED); + newView.setKeyguardCallback(mCallback); + } + + // Find and show this child. + final int childCount = mSecurityViewFlipper.getChildCount(); + + final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); + for (int i = 0; i < childCount; i++) { + if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) { + mSecurityViewFlipper.setDisplayedChild(i); + break; + } + } + + mCurrentSecuritySelection = securityMode; + mCurrentSecurityView = newView; + mSecurityCallback.onSecurityModeChanged(securityMode, + securityMode != SecurityMode.None && newView.needsInput()); + } + + private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { + public void userActivity() { + if (mSecurityCallback != null) { + mSecurityCallback.userActivity(); + } + } + + @Override + public void onUserInput() { + mUpdateMonitor.cancelFaceAuth(); + } + + @Override + public void dismiss(boolean authenticated, int targetId) { + dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); + } + + @Override + public void dismiss(boolean authenticated, int targetId, + boolean bypassSecondaryLockScreen) { + mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); + } + + public boolean isVerifyUnlockOnly() { + return mIsVerifyUnlockOnly; + } + + public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { + if (success) { + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); + mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); + // Force a garbage collection in an attempt to erase any lockscreen password left in + // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard + // dismiss animation janky. + ThreadUtils.postOnBackgroundThread(() -> { + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { } + Runtime.getRuntime().gc(); + }); + } else { + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); + KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs); + } + mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) + .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); + sUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS + : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE); + } + + public void reset() { + mSecurityCallback.reset(); + } + + public void onCancelClicked() { + mSecurityCallback.onCancelClicked(); + } + }; + + // The following is used to ignore callbacks from SecurityViews that are no longer current + // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the + // state for the current security method. + private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { + @Override + public void userActivity() { } + @Override + public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { } + @Override + public boolean isVerifyUnlockOnly() { return false; } + @Override + public void dismiss(boolean securityVerified, int targetUserId) { } + @Override + public void dismiss(boolean authenticated, int targetId, + boolean bypassSecondaryLockScreen) { } + @Override + public void onUserInput() { } + @Override + public void reset() {} + }; + + private int getSecurityViewIdForMode(SecurityMode securityMode) { + switch (securityMode) { + case Pattern: return R.id.keyguard_pattern_view; + case PIN: return R.id.keyguard_pin_view; + case Password: return R.id.keyguard_password_view; + case SimPin: return R.id.keyguard_sim_pin_view; + case SimPuk: return R.id.keyguard_sim_puk_view; + } + return 0; + } + + @VisibleForTesting + public int getLayoutIdFor(SecurityMode securityMode) { + switch (securityMode) { + case Pattern: return R.layout.keyguard_pattern_view; + case PIN: return R.layout.keyguard_pin_view; + case Password: return R.layout.keyguard_password_view; + case SimPin: return R.layout.keyguard_sim_pin_view; + case SimPuk: return R.layout.keyguard_sim_puk_view; + default: + return 0; + } + } + + public SecurityMode getSecurityMode() { + return mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()); + } + + public SecurityMode getCurrentSecurityMode() { + return mCurrentSecuritySelection; + } + + public KeyguardSecurityView getCurrentSecurityView() { + return mCurrentSecurityView; + } + + public void verifyUnlock() { + mIsVerifyUnlockOnly = true; + showSecurityScreen(getSecurityMode()); + } + + public SecurityMode getCurrentSecuritySelection() { + return mCurrentSecuritySelection; + } + + public void dismiss(boolean authenticated, int targetUserId) { + mCallback.dismiss(authenticated, targetUserId); + } + + public boolean needsInput() { + return mSecurityViewFlipper.needsInput(); + } + + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mSecurityViewFlipper.setKeyguardCallback(callback); + } + + @Override public void reset() { + mSecurityViewFlipper.reset(); mDisappearAnimRunning = false; } + + @Override + public KeyguardSecurityCallback getCallback() { + return mSecurityViewFlipper.getCallback(); + } + + @Override + public void showPromptReason(int reason) { + if (mCurrentSecuritySelection != SecurityMode.None) { + if (reason != PROMPT_REASON_NONE) { + Log.i(TAG, "Strong auth required, reason: " + reason); + } + getSecurityView(mCurrentSecuritySelection).showPromptReason(reason); + } + } + + public void showMessage(CharSequence message, ColorStateList colorState) { + if (mCurrentSecuritySelection != SecurityMode.None) { + getSecurityView(mCurrentSecuritySelection).showMessage(message, colorState); + } + } + + @Override + public void showUsabilityHint() { + mSecurityViewFlipper.showUsabilityHint(); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 64676e55b038..17f25bd08ef4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -16,166 +16,33 @@ package com.android.keyguard; -import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; -import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; -import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; -import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD; -import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM; -import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY; -import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER; -import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE; -import static com.android.systemui.DejankUtils.whitelistIpcs; - -import android.app.admin.DevicePolicyManager; -import android.content.Intent; import android.content.res.ColorStateList; -import android.metrics.LogMaker; -import android.os.UserHandle; -import android.util.Log; -import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.nano.MetricsProto; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent; import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; -import com.android.keyguard.KeyguardSecurityContainer.SwipeListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.keyguard.dagger.KeyguardBouncerScope; -import com.android.settingslib.utils.ThreadUtils; -import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import javax.inject.Inject; /** Controller for {@link KeyguardSecurityContainer} */ -@KeyguardBouncerScope -public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> - implements KeyguardSecurityView { - - private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final String TAG = "KeyguardSecurityView"; +public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> { - private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; private final LockPatternUtils mLockPatternUtils; - private final KeyguardUpdateMonitor mUpdateMonitor; - private final KeyguardSecurityModel mSecurityModel; - private final MetricsLogger mMetricsLogger; - private final UiEventLogger mUiEventLogger; - private final KeyguardStateController mKeyguardStateController; - private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; - - private SecurityCallback mSecurityCallback; - private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; - - private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() { - public void userActivity() { - if (mSecurityCallback != null) { - mSecurityCallback.userActivity(); - } - } - - @Override - public void onUserInput() { - mUpdateMonitor.cancelFaceAuth(); - } - - @Override - public void dismiss(boolean authenticated, int targetId) { - dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); - } - - @Override - public void dismiss(boolean authenticated, int targetId, - boolean bypassSecondaryLockScreen) { - mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); - } - - public boolean isVerifyUnlockOnly() { - return false; - } - - public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { - if (success) { - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); - mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); - // Force a garbage collection in an attempt to erase any lockscreen password left in - // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard - // dismiss animation janky. - ThreadUtils.postOnBackgroundThread(() -> { - try { - Thread.sleep(5000); - } catch (InterruptedException ignored) { } - Runtime.getRuntime().gc(); - }); - } else { - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); - reportFailedUnlockAttempt(userId, timeoutMs); - } - mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) - .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); - mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS - : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE); - } - - public void reset() { - mSecurityCallback.reset(); - } - - public void onCancelClicked() { - mSecurityCallback.onCancelClicked(); - } - }; - - - private SwipeListener mSwipeListener = new SwipeListener() { - @Override - public void onSwipeUp() { - if (!mUpdateMonitor.isFaceDetectionRunning()) { - mUpdateMonitor.requestFaceAuth(); - mKeyguardSecurityCallback.userActivity(); - showMessage(null, null); - } - } - }; + private final KeyguardSecurityViewController.Factory mKeyguardSecurityViewControllerFactory; @Inject KeyguardSecurityContainerController(KeyguardSecurityContainer view, - AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory, LockPatternUtils lockPatternUtils, - KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardSecurityModel keyguardSecurityModel, - MetricsLogger metricsLogger, - UiEventLogger uiEventLogger, - KeyguardStateController keyguardStateController, - KeyguardSecurityViewFlipperController securityViewFlipperController) { + KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) { super(view); mLockPatternUtils = lockPatternUtils; - mUpdateMonitor = keyguardUpdateMonitor; - mSecurityModel = keyguardSecurityModel; - mMetricsLogger = metricsLogger; - mUiEventLogger = uiEventLogger; - mKeyguardStateController = keyguardStateController; - mSecurityViewFlipperController = securityViewFlipperController; - mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( - mKeyguardSecurityCallback); - } - - @Override - public void init() { - super.init(); - mSecurityViewFlipperController.init(); + view.setLockPatternUtils(mLockPatternUtils); + mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; } @Override protected void onViewAttached() { - mView.setSwipeListener(mSwipeListener); } @Override @@ -184,270 +51,68 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** */ public void onPause() { - mAdminSecondaryLockScreenController.hide(); - if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().onPause(); - } mView.onPause(); } - - /** - * Shows the primary security screen for the user. This will be either the multi-selector - * or the user's security method. - * @param turningOff true if the device is being turned off - */ public void showPrimarySecurityScreen(boolean turningOff) { - SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser())); - if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); - showSecurityScreen(securityMode); + mView.showPrimarySecurityScreen(turningOff); } - @Override public void showPromptReason(int reason) { - if (mCurrentSecurityMode != SecurityMode.None) { - if (reason != PROMPT_REASON_NONE) { - Log.i(TAG, "Strong auth required, reason: " + reason); - } - getCurrentSecurityController().showPromptReason(reason); - } + mView.showPromptReason(reason); } public void showMessage(CharSequence message, ColorStateList colorState) { - if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().showMessage(message, colorState); - } + mView.showMessage(message, colorState); } - public SecurityMode getCurrentSecurityMode() { - return mCurrentSecurityMode; + public SecurityMode getCurrentSecuritySelection() { + return mView.getCurrentSecuritySelection(); } public void dismiss(boolean authenticated, int targetUserId) { - mKeyguardSecurityCallback.dismiss(authenticated, targetUserId); + mView.dismiss(authenticated, targetUserId); } public void reset() { mView.reset(); - mSecurityViewFlipperController.reset(); } public CharSequence getTitle() { return mView.getTitle(); } - @Override - public void onResume(int reason) { - if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().onResume(reason); - } - mView.onResume( - mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), - mKeyguardStateController.isFaceAuthEnabled()); + public void onResume(int screenOn) { + mView.onResume(screenOn); } public void startAppearAnimation() { - if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().startAppearAnimation(); - } + mView.startAppearAnimation(); } public boolean startDisappearAnimation(Runnable onFinishRunnable) { - mView.startDisappearAnimation(getCurrentSecurityMode()); - - if (mCurrentSecurityMode != SecurityMode.None) { - return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable); - } - - return false; + return mView.startDisappearAnimation(onFinishRunnable); } public void onStartingToHide() { - if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().onStartingToHide(); - } + mView.onStartingToHide(); } public void setSecurityCallback(SecurityCallback securityCallback) { - mSecurityCallback = securityCallback; + mView.setSecurityCallback(securityCallback); } - /** - * Shows the next security screen if there is one. - * @param authenticated true if the user entered the correct authentication - * @param targetUserId a user that needs to be the foreground user at the finish (if called) - * completion. - * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary - * secondary lock screen requirement, if any. - * @return true if keyguard is done - */ public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen) { - - if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); - boolean finish = false; - boolean strongAuth = false; - int eventSubtype = -1; - BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN; - if (mUpdateMonitor.getUserHasTrust(targetUserId)) { - finish = true; - eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS; - } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { - finish = true; - eventSubtype = BOUNCER_DISMISS_BIOMETRIC; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC; - } else if (SecurityMode.None == getCurrentSecurityMode()) { - SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (SecurityMode.None == securityMode) { - finish = true; // no security required - eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY; - } else { - showSecurityScreen(securityMode); // switch to the alternate security view - } - } else if (authenticated) { - switch (getCurrentSecurityMode()) { - case Pattern: - case Password: - case PIN: - strongAuth = true; - finish = true; - eventSubtype = BOUNCER_DISMISS_PASSWORD; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD; - break; - - case SimPin: - case SimPuk: - // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home - SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser())) { - finish = true; - eventSubtype = BOUNCER_DISMISS_SIM; - uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; - } else { - showSecurityScreen(securityMode); - } - break; - - default: - Log.v(TAG, "Bad security screen " + getCurrentSecurityMode() - + ", fail safe"); - showPrimarySecurityScreen(false); - break; - } - } - // Check for device admin specified additional security measures. - if (finish && !bypassSecondaryLockScreen) { - Intent secondaryLockscreenIntent = - mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); - if (secondaryLockscreenIntent != null) { - mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent); - return false; - } - } - if (eventSubtype != -1) { - mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER) - .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); - } - if (uiEvent != BouncerUiEvent.UNKNOWN) { - mUiEventLogger.log(uiEvent); - } - if (finish) { - mSecurityCallback.finish(strongAuth, targetUserId); - } - return finish; + return mView.showNextSecurityScreenOrFinish( + authenticated, targetUserId, bypassSecondaryLockScreen); } public boolean needsInput() { - return getCurrentSecurityController().needsInput(); - } - - /** - * Switches to the given security view unless it's already being shown, in which case - * this is a no-op. - * - * @param securityMode - */ - @VisibleForTesting - void showSecurityScreen(SecurityMode securityMode) { - if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); - - if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) { - return; - } - - KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController(); - - // Emulate Activity life cycle - if (oldView != null) { - oldView.onPause(); - } - - KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode); - if (newView != null) { - newView.onResume(KeyguardSecurityView.VIEW_REVEALED); - mSecurityViewFlipperController.show(newView); - } - - mSecurityCallback.onSecurityModeChanged( - securityMode, newView != null && newView.needsInput()); - } - - public void reportFailedUnlockAttempt(int userId, int timeoutMs) { - // +1 for this time - final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1; - - if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); - - final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); - final int failedAttemptsBeforeWipe = - dpm.getMaximumFailedPasswordsForWipe(null, userId); - - final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 - ? (failedAttemptsBeforeWipe - failedAttempts) - : Integer.MAX_VALUE; // because DPM returns 0 if no restriction - if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { - // The user has installed a DevicePolicyManager that requests a user/profile to be wiped - // N attempts. Once we get below the grace period, we post this dialog every time as a - // clear warning until the deletion fires. - // Check which profile has the strictest policy for failed password attempts - final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); - int userType = USER_TYPE_PRIMARY; - if (expiringUser == userId) { - // TODO: http://b/23522538 - if (expiringUser != UserHandle.USER_SYSTEM) { - userType = USER_TYPE_SECONDARY_USER; - } - } else if (expiringUser != UserHandle.USER_NULL) { - userType = USER_TYPE_WORK_PROFILE; - } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY - if (remainingBeforeWipe > 0) { - mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); - } else { - // Too many attempts. The device will be wiped shortly. - Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); - mView.showWipeDialog(failedAttempts, userType); - } - } - mLockPatternUtils.reportFailedPasswordAttempt(userId); - if (timeoutMs > 0) { - mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); - mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils, - mSecurityModel.getSecurityMode(userId)); - } + return mView.needsInput(); } - private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() { - return mSecurityViewFlipperController - .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback); - } - - private KeyguardInputViewController<KeyguardInputView> changeSecurityMode( - SecurityMode securityMode) { - mCurrentSecurityMode = securityMode; - return getCurrentSecurityController(); + public SecurityMode getCurrentSecurityMode() { + return mView.getCurrentSecurityMode(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index c77c86711abf..ac2160ecb4ae 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -18,14 +18,13 @@ package com.android.keyguard; import static com.android.systemui.DejankUtils.whitelistIpcs; import android.app.admin.DevicePolicyManager; -import android.content.res.Resources; +import android.content.Context; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import javax.inject.Inject; @@ -34,7 +33,7 @@ public class KeyguardSecurityModel { /** * The different types of security available. - * @see KeyguardSecurityContainerController#showSecurityScreen + * @see KeyguardSecurityContainer#showSecurityScreen */ public enum SecurityMode { Invalid, // NULL state @@ -46,15 +45,21 @@ public class KeyguardSecurityModel { SimPuk // Unlock by entering a sim puk } + private final Context mContext; private final boolean mIsPukScreenAvailable; - private final LockPatternUtils mLockPatternUtils; + private LockPatternUtils mLockPatternUtils; @Inject - KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) { - mIsPukScreenAvailable = resources.getBoolean( + KeyguardSecurityModel(Context context) { + mContext = context; + mLockPatternUtils = new LockPatternUtils(context); + mIsPukScreenAvailable = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enable_puk_unlock_screen); - mLockPatternUtils = lockPatternUtils; + } + + void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; } public SecurityMode getSecurityMode(int userId) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index ac00e9453c97..43cef3acf147 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -18,9 +18,11 @@ package com.android.keyguard; import android.content.res.ColorStateList; import android.view.MotionEvent; +import com.android.internal.widget.LockPatternUtils; + public interface KeyguardSecurityView { - int SCREEN_ON = 1; - int VIEW_REVEALED = 2; + static public final int SCREEN_ON = 1; + static public final int VIEW_REVEALED = 2; int PROMPT_REASON_NONE = 0; @@ -61,6 +63,18 @@ public interface KeyguardSecurityView { int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7; /** + * Interface back to keyguard to tell it when security + * @param callback + */ + void setKeyguardCallback(KeyguardSecurityCallback callback); + + /** + * Set {@link LockPatternUtils} object. Useful for providing a mock interface. + * @param utils + */ + void setLockPatternUtils(LockPatternUtils utils); + + /** * Reset the view and prepare to take input. This should do things like clearing the * password or pattern and clear error messages. */ @@ -87,6 +101,12 @@ public interface KeyguardSecurityView { boolean needsInput(); /** + * Get {@link KeyguardSecurityCallback} for the given object + * @return KeyguardSecurityCallback + */ + KeyguardSecurityCallback getCallback(); + + /** * Show a string explaining why the security view needs to be solved. * * @param reason a flag indicating which string should be shown, see {@link #PROMPT_REASON_NONE} @@ -103,6 +123,12 @@ public interface KeyguardSecurityView { void showMessage(CharSequence message, ColorStateList colorState); /** + * Instruct the view to show usability hints, if any. + * + */ + void showUsabilityHint(); + + /** * Starts the animation which should run when the security view appears. */ void startAppearAnimation(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java new file mode 100644 index 000000000000..ef9ba19fbb43 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.view.View; + +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; + + +/** Controller for a {@link KeyguardSecurityView}. */ +public class KeyguardSecurityViewController extends ViewController<View> { + + private final KeyguardSecurityView mView; + + private KeyguardSecurityViewController(KeyguardSecurityView view) { + super((View) view); + // KeyguardSecurityView isn't actually a View, so we need to track it ourselves. + mView = view; + } + + @Override + protected void onViewAttached() { + + } + + @Override + protected void onViewDetached() { + + } + + /** Factory for a {@link KeyguardSecurityViewController}. */ + public static class Factory { + @Inject + public Factory() { + } + + /** Create a new {@link KeyguardSecurityViewController}. */ + public KeyguardSecurityViewController create(KeyguardSecurityView view) { + return new KeyguardSecurityViewController(view); + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java index b8439af6daaa..24da3ad46f23 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java @@ -18,6 +18,7 @@ package com.android.keyguard; import android.annotation.NonNull; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; @@ -30,6 +31,7 @@ import android.view.ViewHierarchyEncoder; import android.widget.FrameLayout; import android.widget.ViewFlipper; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; /** @@ -37,7 +39,7 @@ import com.android.systemui.R; * we can emulate {@link android.view.WindowManager.LayoutParams#FLAG_SLIPPERY} within a view * hierarchy. */ -public class KeyguardSecurityViewFlipper extends ViewFlipper { +public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView { private static final String TAG = "KeyguardSecurityViewFlipper"; private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -67,16 +69,111 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper { return result; } - KeyguardInputView getSecurityView() { + KeyguardSecurityView getSecurityView() { View child = getChildAt(getDisplayedChild()); - if (child instanceof KeyguardInputView) { - return (KeyguardInputView) child; + if (child instanceof KeyguardSecurityView) { + return (KeyguardSecurityView) child; } return null; } + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.setKeyguardCallback(callback); + } + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.setLockPatternUtils(utils); + } + } + + @Override + public void reset() { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.reset(); + } + } + + @Override + public void onPause() { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.onPause(); + } + } + + @Override + public void onResume(int reason) { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.onResume(reason); + } + } + + @Override + public boolean needsInput() { + KeyguardSecurityView ksv = getSecurityView(); + return (ksv != null) ? ksv.needsInput() : false; + } + + @Override + public KeyguardSecurityCallback getCallback() { + KeyguardSecurityView ksv = getSecurityView(); + return (ksv != null) ? ksv.getCallback() : null; + } + + @Override + public void showPromptReason(int reason) { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.showPromptReason(reason); + } + } + + @Override + public void showMessage(CharSequence message, ColorStateList colorState) { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.showMessage(message, colorState); + } + } + + @Override + public void showUsabilityHint() { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.showUsabilityHint(); + } + } + + @Override + public void startAppearAnimation() { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.startAppearAnimation(); + } + } + + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + return ksv.startDisappearAnimation(finishRunnable); + } else { + return false; + } + } + + @Override public CharSequence getTitle() { - KeyguardInputView ksv = getSecurityView(); + KeyguardSecurityView ksv = getSecurityView(); if (ksv != null) { return ksv.getTitle(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java deleted file mode 100644 index 49530355a6fb..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.util.Log; -import android.view.LayoutInflater; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.keyguard.KeyguardInputViewController.Factory; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.keyguard.dagger.KeyguardBouncerScope; -import com.android.systemui.R; -import com.android.systemui.util.ViewController; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Controller for a {@link KeyguardSecurityViewFlipper}. - */ -@KeyguardBouncerScope -public class KeyguardSecurityViewFlipperController - extends ViewController<KeyguardSecurityViewFlipper> { - - private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final String TAG = "KeyguardSecurityView"; - - private final List<KeyguardInputViewController<KeyguardInputView>> mChildren = - new ArrayList<>(); - private final LayoutInflater mLayoutInflater; - private final Factory mKeyguardSecurityViewControllerFactory; - - @Inject - protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view, - LayoutInflater layoutInflater, - KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) { - super(view); - mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; - mLayoutInflater = layoutInflater; - } - - @Override - protected void onViewAttached() { - - } - - @Override - protected void onViewDetached() { - - } - - public void reset() { - for (KeyguardInputViewController<KeyguardInputView> child : mChildren) { - child.reset(); - } - } - - @VisibleForTesting - KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback) { - KeyguardInputViewController<KeyguardInputView> childController = null; - for (KeyguardInputViewController<KeyguardInputView> child : mChildren) { - if (child.getSecurityMode() == securityMode) { - childController = child; - break; - } - } - - if (childController == null - && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) { - - int layoutId = getLayoutIdFor(securityMode); - KeyguardInputView view = null; - if (layoutId != 0) { - if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); - view = (KeyguardInputView) mLayoutInflater.inflate( - layoutId, mView, false); - mView.addView(view); - childController = mKeyguardSecurityViewControllerFactory.create( - view, securityMode, keyguardSecurityCallback); - childController.init(); - - mChildren.add(childController); - } - } - - if (childController == null) { - childController = new NullKeyguardInputViewController( - securityMode, keyguardSecurityCallback); - } - - return childController; - } - - private int getLayoutIdFor(SecurityMode securityMode) { - switch (securityMode) { - case Pattern: return com.android.systemui.R.layout.keyguard_pattern_view; - case PIN: return com.android.systemui.R.layout.keyguard_pin_view; - case Password: return com.android.systemui.R.layout.keyguard_password_view; - case SimPin: return com.android.systemui.R.layout.keyguard_sim_pin_view; - case SimPuk: return R.layout.keyguard_sim_puk_view; - default: - return 0; - } - } - - /** Makes the supplied child visible if it is contained win this view, */ - public void show(KeyguardInputViewController<KeyguardInputView> childController) { - int index = childController.getIndexIn(mView); - if (index != -1) { - mView.setDisplayedChild(index); - } - } - - private static class NullKeyguardInputViewController - extends KeyguardInputViewController<KeyguardInputView> { - protected NullKeyguardInputViewController(SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback) { - super(null, securityMode, keyguardSecurityCallback); - } - - @Override - public boolean needsInput() { - return false; - } - - @Override - public void onStartingToHide() { - - } - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index c0f9ce794628..1c47aa0151f0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -16,19 +16,66 @@ package com.android.keyguard; +import android.annotation.NonNull; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.ProgressDialog; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.telephony.PinResult; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.util.AttributeSet; +import android.util.Log; import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import com.android.systemui.Dependency; import com.android.systemui.R; /** * Displays a PIN pad for unlocking. */ public class KeyguardSimPinView extends KeyguardPinBasedInputView { + private static final String LOG_TAG = "KeyguardSimPinView"; + private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; public static final String TAG = "KeyguardSimPinView"; + private ProgressDialog mSimUnlockProgressDialog = null; + private CheckSimPin mCheckSimPinThread; + + // Below flag is set to true during power-up or when a new SIM card inserted on device. + // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would + // be displayed to inform user about the number of remaining PIN attempts left. + private boolean mShowDefaultMessage = true; + private int mRemainingAttempts = -1; + private AlertDialog mRemainingAttemptsDialog; + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private ImageView mSimImageView; + + KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + switch(simState) { + case TelephonyManager.SIM_STATE_READY: { + mRemainingAttempts = -1; + resetState(); + break; + } + default: + resetState(); + } + } + }; + public KeyguardSimPinView(Context context) { this(context, null); } @@ -37,9 +84,81 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { super(context, attrs); } - public void setEsimLocked(boolean locked) { + @Override + public void resetState() { + super.resetState(); + if (DEBUG) Log.v(TAG, "Resetting state"); + handleSubInfoChangeIfNeeded(); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); + KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setVisibility(locked ? View.VISIBLE : View.GONE); + esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + } + + private void setLockedSimMessage() { + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); + int count = 1; + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager != null) { + count = telephonyManager.getActiveModemCount(); + } + Resources rez = getResources(); + String msg; + TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); + int color = array.getColor(0, Color.WHITE); + array.recycle(); + if (count < 2) { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } else { + SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) + .getSubscriptionInfoForSubId(mSubId); + CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (info != null) { + color = info.getIconTint(); + } + } + if (isEsimLocked) { + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); + } + + if (mSecurityMessageDisplay != null && getVisibility() == VISIBLE) { + mSecurityMessageDisplay.setMessage(msg); + } + mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + } + + private void showDefaultMessage() { + setLockedSimMessage(); + if (mRemainingAttempts >= 0) { + return; + } + + // Sending empty PIN here to query the number of remaining PIN attempts + new CheckSimPin("", mSubId) { + void onSimCheckResponse(final PinResult result) { + Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + setLockedSimMessage(); + } + } + }.start(); + } + + private void handleSubInfoChangeIfNeeded() { + KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); + int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); + if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { + mSubId = subId; + mShowDefaultMessage = true; + mRemainingAttempts = -1; + } } @Override @@ -54,6 +173,35 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { return 0; } + private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { + String displayMessage; + int msgId; + if (attemptsRemaining == 0) { + displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked); + } else if (attemptsRemaining > 0) { + msgId = isDefault ? R.plurals.kg_password_default_pin_message : + R.plurals.kg_password_wrong_pin_code; + displayMessage = getContext().getResources() + .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); + } else { + msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; + displayMessage = getContext().getString(msgId); + } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } + if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" + + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); + return displayMessage; + } + + @Override + protected boolean shouldLockout(long deadline) { + // SIM PIN doesn't have a timed lockout + return false; + } + @Override protected int getPasswordTextViewId() { return R.id.simPinEntry; @@ -66,6 +214,173 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { if (mEcaView instanceof EmergencyCarrierArea) { ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); } + mSimImageView = findViewById(R.id.keyguard_sim); + } + + @Override + public void showUsabilityHint() { + + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); + resetState(); + } + + @Override + public void onPause() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPin extends Thread { + private final String mPin; + private int mSubId; + + protected CheckSimPin(String pin, int subId) { + mPin = pin; + mSubId = subId; + } + + abstract void onSimCheckResponse(@NonNull PinResult result); + + @Override + public void run() { + if (DEBUG) { + Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); + } + TelephonyManager telephonyManager = + ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) + .createForSubscriptionId(mSubId); + final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); + if (result == null) { + Log.e(TAG, "Error result for supplyPinReportResult."); + post(new Runnable() { + @Override + public void run() { + onSimCheckResponse(PinResult.getDefaultFailedResult()); + } + }); + } else { + if (DEBUG) { + Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); + } + post(new Runnable() { + @Override + public void run() { + onSimCheckResponse(result); + } + }); + } + } + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage( + mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mSimUnlockProgressDialog; + } + + private Dialog getSimRemainingAttemptsDialog(int remaining) { + String msg = getPinPasswordErrorMessage(remaining, false); + if (mRemainingAttemptsDialog == null) { + Builder builder = new AlertDialog.Builder(mContext); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + + @Override + protected void verifyPasswordAndUnlock() { + String entry = mPasswordEntry.getText(); + + if (entry.length() < 4) { + // otherwise, display a message to the user, and don't submit. + mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint); + resetPasswordText(true /* animate */, true /* announce */); + mCallback.userActivity(); + return; + } + + getSimUnlockProgressDialog().show(); + + if (mCheckSimPinThread == null) { + mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { + @Override + void onSimCheckResponse(final PinResult result) { + post(new Runnable() { + @Override + public void run() { + mRemainingAttempts = result.getAttemptsRemaining(); + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + resetPasswordText(true /* animate */, + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { + Dependency.get(KeyguardUpdateMonitor.class) + .reportSimUnlocked(mSubId); + mRemainingAttempts = -1; + mShowDefaultMessage = true; + if (mCallback != null) { + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + } + } else { + mShowDefaultMessage = false; + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + if (result.getAttemptsRemaining() <= 2) { + // this is getting critical - show dialog + getSimRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); + } else { + // show message + mSecurityMessageDisplay.setMessage( + getPinPasswordErrorMessage( + result.getAttemptsRemaining(), false)); + } + } else { + // "PIN operation failed!" - no idea what this was and no way to + // find out. :/ + mSecurityMessageDisplay.setMessage(getContext().getString( + R.string.kg_password_pin_failed)); + } + if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " CheckSimPin.onSimCheckResponse: " + result + + " attemptsRemaining=" + result.getAttemptsRemaining()); + } + mCallback.userActivity(); + mCheckSimPinThread = null; + } + }); + } + }; + mCheckSimPinThread.start(); + } } @Override @@ -74,6 +389,11 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return false; + } + + @Override public CharSequence getTitle() { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java deleted file mode 100644 index cc8bf4f2d028..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.annotation.NonNull; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.telephony.PinResult; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; - -public class KeyguardSimPinViewController - extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { - public static final String TAG = "KeyguardSimPinView"; - private static final String LOG_TAG = "KeyguardSimPinView"; - private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final TelephonyManager mTelephonyManager; - - private ProgressDialog mSimUnlockProgressDialog; - private CheckSimPin mCheckSimPinThread; - private int mRemainingAttempts; - // Below flag is set to true during power-up or when a new SIM card inserted on device. - // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would - // be displayed to inform user about the number of remaining PIN attempts left. - private boolean mShowDefaultMessage; - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private AlertDialog mRemainingAttemptsDialog; - private ImageView mSimImageView; - - KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onSimStateChanged(int subId, int slotId, int simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - if (simState == TelephonyManager.SIM_STATE_READY) { - mRemainingAttempts = -1; - resetState(); - } else { - resetState(); - } - } - }; - - protected KeyguardSimPinViewController(KeyguardSimPinView view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, - LiftToActivateListener liftToActivateListener, - TelephonyManager telephonyManager) { - super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, liftToActivateListener); - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mTelephonyManager = telephonyManager; - mSimImageView = mView.findViewById(R.id.keyguard_sim); - } - - @Override - protected void onViewAttached() { - super.onViewAttached(); - } - - @Override - void resetState() { - super.resetState(); - if (DEBUG) Log.v(TAG, "Resetting state"); - handleSubInfoChangeIfNeeded(); - mMessageAreaController.setMessage(""); - if (mShowDefaultMessage) { - showDefaultMessage(); - } - - mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); - } - - @Override - public boolean startDisappearAnimation(Runnable finishRunnable) { - return false; - } - - @Override - public void onResume(int reason) { - super.onResume(reason); - mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); - mView.resetState(); - } - - @Override - public void onPause() { - super.onPause(); - mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); - - // dismiss the dialog. - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.dismiss(); - mSimUnlockProgressDialog = null; - } - } - - @Override - protected void verifyPasswordAndUnlock() { - String entry = mPasswordEntry.getText(); - - if (entry.length() < 4) { - // otherwise, display a message to the user, and don't submit. - mMessageAreaController.setMessage( - com.android.systemui.R.string.kg_invalid_sim_pin_hint); - mView.resetPasswordText(true /* animate */, true /* announce */); - getKeyguardSecurityCallback().userActivity(); - return; - } - - getSimUnlockProgressDialog().show(); - - if (mCheckSimPinThread == null) { - mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { - @Override - void onSimCheckResponse(final PinResult result) { - mView.post(() -> { - mRemainingAttempts = result.getAttemptsRemaining(); - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.hide(); - } - mView.resetPasswordText(true /* animate */, - /* announce */ - result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); - if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { - mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); - mRemainingAttempts = -1; - mShowDefaultMessage = true; - getKeyguardSecurityCallback().dismiss( - true, KeyguardUpdateMonitor.getCurrentUser()); - } else { - mShowDefaultMessage = false; - if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { - if (result.getAttemptsRemaining() <= 2) { - // this is getting critical - show dialog - getSimRemainingAttemptsDialog( - result.getAttemptsRemaining()).show(); - } else { - // show message - mMessageAreaController.setMessage( - getPinPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - } - } else { - // "PIN operation failed!" - no idea what this was and no way to - // find out. :/ - mMessageAreaController.setMessage(mView.getResources().getString( - R.string.kg_password_pin_failed)); - } - if (DEBUG) { - Log.d(LOG_TAG, "verifyPasswordAndUnlock " - + " CheckSimPin.onSimCheckResponse: " + result - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } - } - getKeyguardSecurityCallback().userActivity(); - mCheckSimPinThread = null; - }); - } - }; - mCheckSimPinThread.start(); - } - } - - private Dialog getSimUnlockProgressDialog() { - if (mSimUnlockProgressDialog == null) { - mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); - mSimUnlockProgressDialog.setMessage( - mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); - mSimUnlockProgressDialog.setIndeterminate(true); - mSimUnlockProgressDialog.setCancelable(false); - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } - return mSimUnlockProgressDialog; - } - - - private Dialog getSimRemainingAttemptsDialog(int remaining) { - String msg = getPinPasswordErrorMessage(remaining, false); - if (mRemainingAttemptsDialog == null) { - Builder builder = new AlertDialog.Builder(mView.getContext()); - builder.setMessage(msg); - builder.setCancelable(false); - builder.setNeutralButton(R.string.ok, null); - mRemainingAttemptsDialog = builder.create(); - mRemainingAttemptsDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } else { - mRemainingAttemptsDialog.setMessage(msg); - } - return mRemainingAttemptsDialog; - } - - - private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { - String displayMessage; - int msgId; - if (attemptsRemaining == 0) { - displayMessage = mView.getResources().getString( - R.string.kg_password_wrong_pin_code_pukked); - } else if (attemptsRemaining > 0) { - msgId = isDefault ? R.plurals.kg_password_default_pin_message : - R.plurals.kg_password_wrong_pin_code; - displayMessage = mView.getResources() - .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); - } else { - msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; - displayMessage = mView.getResources().getString(msgId); - } - if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) { - displayMessage = mView.getResources() - .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); - } - if (DEBUG) { - Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining=" - + attemptsRemaining + " displayMessage=" + displayMessage); - } - return displayMessage; - } - - private void showDefaultMessage() { - setLockedSimMessage(); - if (mRemainingAttempts >= 0) { - return; - } - - // Sending empty PIN here to query the number of remaining PIN attempts - new CheckSimPin("", mSubId) { - void onSimCheckResponse(final PinResult result) { - Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " - + result.toString()); - if (result.getAttemptsRemaining() >= 0) { - mRemainingAttempts = result.getAttemptsRemaining(); - setLockedSimMessage(); - } - } - }.start(); - } - - /** - * Since the IPC can block, we want to run the request in a separate thread - * with a callback. - */ - private abstract class CheckSimPin extends Thread { - private final String mPin; - private int mSubId; - - protected CheckSimPin(String pin, int subId) { - mPin = pin; - mSubId = subId; - } - - abstract void onSimCheckResponse(@NonNull PinResult result); - - @Override - public void run() { - if (DEBUG) { - Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); - } - TelephonyManager telephonyManager = - mTelephonyManager.createForSubscriptionId(mSubId); - final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); - if (result == null) { - Log.e(TAG, "Error result for supplyPinReportResult."); - mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult())); - } else { - if (DEBUG) { - Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); - } - mView.post(() -> onSimCheckResponse(result)); - } - } - } - - private void setLockedSimMessage() { - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); - int count = 1; - if (mTelephonyManager != null) { - count = mTelephonyManager.getActiveModemCount(); - } - Resources rez = mView.getResources(); - String msg; - TypedArray array = mView.getContext().obtainStyledAttributes( - new int[] { R.attr.wallpaperTextColor }); - int color = array.getColor(0, Color.WHITE); - array.recycle(); - if (count < 2) { - msg = rez.getString(R.string.kg_sim_pin_instructions); - } else { - SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); - CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash - msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); - if (info != null) { - color = info.getIconTint(); - } - } - if (isEsimLocked) { - msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); - } - - if (mView.getVisibility() == View.VISIBLE) { - mMessageAreaController.setMessage(msg); - } - mSimImageView.setImageTintList(ColorStateList.valueOf(color)); - } - - private void handleSubInfoChangeIfNeeded() { - int subId = mKeyguardUpdateMonitor - .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); - if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { - mSubId = subId; - mShowDefaultMessage = true; - mRemainingAttempts = -1; - } - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index 0d72c93e9041..5148dd709026 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -16,10 +16,27 @@ package com.android.keyguard; +import android.annotation.NonNull; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.telephony.PinResult; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import com.android.systemui.Dependency; import com.android.systemui.R; @@ -27,9 +44,48 @@ import com.android.systemui.R; * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ public class KeyguardSimPukView extends KeyguardPinBasedInputView { + private static final String LOG_TAG = "KeyguardSimPukView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; public static final String TAG = "KeyguardSimPukView"; + private ProgressDialog mSimUnlockProgressDialog = null; + private CheckSimPuk mCheckSimPukThread; + + // Below flag is set to true during power-up or when a new SIM card inserted on device. + // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would + // be displayed to inform user about the number of remaining PUK attempts left. + private boolean mShowDefaultMessage = true; + private int mRemainingAttempts = -1; + private String mPukText; + private String mPinText; + private StateMachine mStateMachine = new StateMachine(); + private AlertDialog mRemainingAttemptsDialog; + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private ImageView mSimImageView; + + KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + switch(simState) { + // If the SIM is unlocked via a key sequence through the emergency dialer, it will + // move into the READY state and the PUK lock keyguard should be removed. + case TelephonyManager.SIM_STATE_READY: { + mRemainingAttempts = -1; + mShowDefaultMessage = true; + // mCallback can be null if onSimStateChanged callback is called when keyguard + // isn't active. + if (mCallback != null) { + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + } + break; + } + default: + resetState(); + } + } + }; + public KeyguardSimPukView(Context context) { this(context, null); } @@ -38,14 +94,136 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { super(context, attrs); } + private class StateMachine { + final int ENTER_PUK = 0; + final int ENTER_PIN = 1; + final int CONFIRM_PIN = 2; + final int DONE = 3; + private int state = ENTER_PUK; + + public void next() { + int msg = 0; + if (state == ENTER_PUK) { + if (checkPuk()) { + state = ENTER_PIN; + msg = R.string.kg_puk_enter_pin_hint; + } else { + msg = R.string.kg_invalid_sim_puk_hint; + } + } else if (state == ENTER_PIN) { + if (checkPin()) { + state = CONFIRM_PIN; + msg = R.string.kg_enter_confirm_pin_hint; + } else { + msg = R.string.kg_invalid_sim_pin_hint; + } + } else if (state == CONFIRM_PIN) { + if (confirmPin()) { + state = DONE; + msg = R.string.keyguard_sim_unlock_progress_dialog_message; + updateSim(); + } else { + state = ENTER_PIN; // try again? + msg = R.string.kg_invalid_confirm_pin_hint; + } + } + resetPasswordText(true /* animate */, true /* announce */); + if (msg != 0) { + mSecurityMessageDisplay.setMessage(msg); + } + } + + + void reset() { + mPinText=""; + mPukText=""; + state = ENTER_PUK; + handleSubInfoChangeIfNeeded(); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); + + KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); + esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + mPasswordEntry.requestFocus(); + } + + + } + + private void showDefaultMessage() { + if (mRemainingAttempts >= 0) { + mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( + mRemainingAttempts, true)); + return; + } + + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); + int count = 1; + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager != null) { + count = telephonyManager.getActiveModemCount(); + } + Resources rez = getResources(); + String msg; + TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); + int color = array.getColor(0, Color.WHITE); + array.recycle(); + if (count < 2) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } else { + SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) + .getSubscriptionInfoForSubId(mSubId); + CharSequence displayName = info != null ? info.getDisplayName() : ""; + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (info != null) { + color = info.getIconTint(); + } + } + if (isEsimLocked) { + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); + } + if (mSecurityMessageDisplay != null) { + mSecurityMessageDisplay.setMessage(msg); + } + mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + + // Sending empty PUK here to query the number of remaining PIN attempts + new CheckSimPuk("", "", mSubId) { + void onSimLockChangedResponse(final PinResult result) { + if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL"); + else { + Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + mSecurityMessageDisplay.setMessage( + getPukPasswordErrorMessage(result.getAttemptsRemaining(), true)); + } + } + } + }.start(); + } + + private void handleSubInfoChangeIfNeeded() { + KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); + int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED); + if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { + mSubId = subId; + mShowDefaultMessage = true; + mRemainingAttempts = -1; + } + } + @Override protected int getPromptReasonStringRes(int reason) { // No message on SIM Puk return 0; } - String getPukPasswordErrorMessage( - int attemptsRemaining, boolean isDefault, boolean isEsimLocked) { + private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { String displayMessage; if (attemptsRemaining == 0) { @@ -60,19 +238,28 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { R.string.kg_password_puk_failed; displayMessage = getContext().getString(msgId); } - if (isEsimLocked) { + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { displayMessage = getResources() .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); } - if (DEBUG) { - Log.d(TAG, "getPukPasswordErrorMessage:" - + " attemptsRemaining=" + attemptsRemaining - + " displayMessage=" + displayMessage); - } + if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" + + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; } @Override + public void resetState() { + super.resetState(); + mStateMachine.reset(); + } + + @Override + protected boolean shouldLockout(long deadline) { + // SIM PUK doesn't have a timed lockout + return false; + } + + @Override protected int getPasswordTextViewId() { return R.id.pukEntry; } @@ -84,6 +271,197 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { if (mEcaView instanceof EmergencyCarrierArea) { ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); } + mSimImageView = findViewById(R.id.keyguard_sim); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); + resetState(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); + } + + @Override + public void showUsabilityHint() { + } + + @Override + public void onPause() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPuk extends Thread { + + private final String mPin, mPuk; + private final int mSubId; + + protected CheckSimPuk(String puk, String pin, int subId) { + mPuk = puk; + mPin = pin; + mSubId = subId; + } + + abstract void onSimLockChangedResponse(@NonNull PinResult result); + + @Override + public void run() { + if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); + TelephonyManager telephonyManager = + ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) + .createForSubscriptionId(mSubId); + final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); + if (result == null) { + Log.e(TAG, "Error result for supplyPukReportResult."); + post(new Runnable() { + @Override + public void run() { + onSimLockChangedResponse(PinResult.getDefaultFailedResult()); + } + }); + } else { + if (DEBUG) { + Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); + } + post(new Runnable() { + @Override + public void run() { + onSimLockChangedResponse(result); + } + }); + } + } + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage( + mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mContext instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private Dialog getPukRemainingAttemptsDialog(int remaining) { + String msg = getPukPasswordErrorMessage(remaining, false); + if (mRemainingAttemptsDialog == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + + private boolean checkPuk() { + // make sure the puk is at least 8 digits long. + if (mPasswordEntry.getText().length() == 8) { + mPukText = mPasswordEntry.getText(); + return true; + } + return false; + } + + private boolean checkPin() { + // make sure the PIN is between 4 and 8 digits + int length = mPasswordEntry.getText().length(); + if (length >= 4 && length <= 8) { + mPinText = mPasswordEntry.getText(); + return true; + } + return false; + } + + public boolean confirmPin() { + return mPinText.equals(mPasswordEntry.getText()); + } + + private void updateSim() { + getSimUnlockProgressDialog().show(); + + if (mCheckSimPukThread == null) { + mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { + @Override + void onSimLockChangedResponse(final PinResult result) { + post(new Runnable() { + @Override + public void run() { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + resetPasswordText(true /* animate */, + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { + Dependency.get(KeyguardUpdateMonitor.class) + .reportSimUnlocked(mSubId); + mRemainingAttempts = -1; + mShowDefaultMessage = true; + if (mCallback != null) { + mCallback.dismiss(true, + KeyguardUpdateMonitor.getCurrentUser()); + } + } else { + mShowDefaultMessage = false; + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + // show message + mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( + result.getAttemptsRemaining(), false)); + if (result.getAttemptsRemaining() <= 2) { + // this is getting critical - show dialog + getPukRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); + } else { + // show message + mSecurityMessageDisplay.setMessage( + getPukPasswordErrorMessage( + result.getAttemptsRemaining(), false)); + } + } else { + mSecurityMessageDisplay.setMessage(getContext().getString( + R.string.kg_password_puk_failed)); + } + if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " UpdateSim.onSimCheckResponse: " + + " attemptsRemaining=" + result.getAttemptsRemaining()); + } + mStateMachine.reset(); + mCheckSimPukThread = null; + } + }); + } + }; + mCheckSimPukThread.start(); + } + } + + @Override + protected void verifyPasswordAndUnlock() { + mStateMachine.next(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java deleted file mode 100644 index a87374939ba6..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import android.annotation.NonNull; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.telephony.PinResult; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.Dependency; -import com.android.systemui.R; - -public class KeyguardSimPukViewController - extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { - private static final boolean DEBUG = KeyguardConstants.DEBUG; - public static final String TAG = "KeyguardSimPukView"; - - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final TelephonyManager mTelephonyManager; - - private String mPukText; - private String mPinText; - private int mRemainingAttempts; - // Below flag is set to true during power-up or when a new SIM card inserted on device. - // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would - // be displayed to inform user about the number of remaining PUK attempts left. - private boolean mShowDefaultMessage; - private StateMachine mStateMachine = new StateMachine(); - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private CheckSimPuk mCheckSimPukThread; - private ProgressDialog mSimUnlockProgressDialog; - - KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onSimStateChanged(int subId, int slotId, int simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - // If the SIM is unlocked via a key sequence through the emergency dialer, it will - // move into the READY state and the PUK lock keyguard should be removed. - if (simState == TelephonyManager.SIM_STATE_READY) { - mRemainingAttempts = -1; - mShowDefaultMessage = true; - getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); - } else { - resetState(); - } - } - }; - private ImageView mSimImageView; - private AlertDialog mRemainingAttemptsDialog; - - protected KeyguardSimPukViewController(KeyguardSimPukView view, - KeyguardUpdateMonitor keyguardUpdateMonitor, - SecurityMode securityMode, LockPatternUtils lockPatternUtils, - KeyguardSecurityCallback keyguardSecurityCallback, - KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, - LiftToActivateListener liftToActivateListener, - TelephonyManager telephonyManager) { - super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, liftToActivateListener); - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mTelephonyManager = telephonyManager; - mSimImageView = mView.findViewById(R.id.keyguard_sim); - } - - @Override - protected void onViewAttached() { - super.onViewAttached(); - mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); - } - - @Override - protected void onViewDetached() { - super.onViewDetached(); - mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); - } - - @Override - void resetState() { - super.resetState(); - mStateMachine.reset(); - } - - @Override - protected void verifyPasswordAndUnlock() { - mStateMachine.next(); - } - - private class StateMachine { - static final int ENTER_PUK = 0; - static final int ENTER_PIN = 1; - static final int CONFIRM_PIN = 2; - static final int DONE = 3; - - private int mState = ENTER_PUK; - - public void next() { - int msg = 0; - if (mState == ENTER_PUK) { - if (checkPuk()) { - mState = ENTER_PIN; - msg = com.android.systemui.R.string.kg_puk_enter_pin_hint; - } else { - msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint; - } - } else if (mState == ENTER_PIN) { - if (checkPin()) { - mState = CONFIRM_PIN; - msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint; - } else { - msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint; - } - } else if (mState == CONFIRM_PIN) { - if (confirmPin()) { - mState = DONE; - msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message; - updateSim(); - } else { - mState = ENTER_PIN; // try again? - msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint; - } - } - mView.resetPasswordText(true /* animate */, true /* announce */); - if (msg != 0) { - mMessageAreaController.setMessage(msg); - } - } - - - void reset() { - mPinText = ""; - mPukText = ""; - mState = ENTER_PUK; - handleSubInfoChangeIfNeeded(); - if (mShowDefaultMessage) { - showDefaultMessage(); - } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); - - KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); - mPasswordEntry.requestFocus(); - } - } - - private void showDefaultMessage() { - if (mRemainingAttempts >= 0) { - mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( - mRemainingAttempts, true, - KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); - return; - } - - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); - int count = 1; - if (mTelephonyManager != null) { - count = mTelephonyManager.getActiveModemCount(); - } - Resources rez = mView.getResources(); - String msg; - TypedArray array = mView.getContext().obtainStyledAttributes( - new int[] { R.attr.wallpaperTextColor }); - int color = array.getColor(0, Color.WHITE); - array.recycle(); - if (count < 2) { - msg = rez.getString(R.string.kg_puk_enter_puk_hint); - } else { - SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) - .getSubscriptionInfoForSubId(mSubId); - CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); - if (info != null) { - color = info.getIconTint(); - } - } - if (isEsimLocked) { - msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); - } - mMessageAreaController.setMessage(msg); - mSimImageView.setImageTintList(ColorStateList.valueOf(color)); - - // Sending empty PUK here to query the number of remaining PIN attempts - new CheckSimPuk("", "", mSubId) { - void onSimLockChangedResponse(final PinResult result) { - if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL"); - else { - Log.d(TAG, "onSimCheckResponse " + " empty One result " - + result.toString()); - if (result.getAttemptsRemaining() >= 0) { - mRemainingAttempts = result.getAttemptsRemaining(); - mMessageAreaController.setMessage( - mView.getPukPasswordErrorMessage( - result.getAttemptsRemaining(), true, - KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); - } - } - } - }.start(); - } - - private boolean checkPuk() { - // make sure the puk is at least 8 digits long. - if (mPasswordEntry.getText().length() == 8) { - mPukText = mPasswordEntry.getText(); - return true; - } - return false; - } - - private boolean checkPin() { - // make sure the PIN is between 4 and 8 digits - int length = mPasswordEntry.getText().length(); - if (length >= 4 && length <= 8) { - mPinText = mPasswordEntry.getText(); - return true; - } - return false; - } - - public boolean confirmPin() { - return mPinText.equals(mPasswordEntry.getText()); - } - - - - - private void updateSim() { - getSimUnlockProgressDialog().show(); - - if (mCheckSimPukThread == null) { - mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { - @Override - void onSimLockChangedResponse(final PinResult result) { - mView.post(() -> { - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.hide(); - } - mView.resetPasswordText(true /* animate */, - /* announce */ - result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); - if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { - mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); - mRemainingAttempts = -1; - mShowDefaultMessage = true; - - getKeyguardSecurityCallback().dismiss( - true, KeyguardUpdateMonitor.getCurrentUser()); - } else { - mShowDefaultMessage = false; - if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { - // show message - mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( - result.getAttemptsRemaining(), false, - KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); - if (result.getAttemptsRemaining() <= 2) { - // this is getting critical - show dialog - getPukRemainingAttemptsDialog( - result.getAttemptsRemaining()).show(); - } else { - // show message - mMessageAreaController.setMessage( - mView.getPukPasswordErrorMessage( - result.getAttemptsRemaining(), false, - KeyguardEsimArea.isEsimLocked( - mView.getContext(), mSubId))); - } - } else { - mMessageAreaController.setMessage(mView.getResources().getString( - R.string.kg_password_puk_failed)); - } - if (DEBUG) { - Log.d(TAG, "verifyPasswordAndUnlock " - + " UpdateSim.onSimCheckResponse: " - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } - } - mStateMachine.reset(); - mCheckSimPukThread = null; - }); - } - }; - mCheckSimPukThread.start(); - } - } - - @Override - protected boolean shouldLockout(long deadline) { - // SIM PUK doesn't have a timed lockout - return false; - } - - private Dialog getSimUnlockProgressDialog() { - if (mSimUnlockProgressDialog == null) { - mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); - mSimUnlockProgressDialog.setMessage( - mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); - mSimUnlockProgressDialog.setIndeterminate(true); - mSimUnlockProgressDialog.setCancelable(false); - if (!(mView.getContext() instanceof Activity)) { - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } - } - return mSimUnlockProgressDialog; - } - - private void handleSubInfoChangeIfNeeded() { - int subId = mKeyguardUpdateMonitor.getNextSubIdForState( - TelephonyManager.SIM_STATE_PUK_REQUIRED); - if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { - mSubId = subId; - mShowDefaultMessage = true; - mRemainingAttempts = -1; - } - } - - - private Dialog getPukRemainingAttemptsDialog(int remaining) { - String msg = mView.getPukPasswordErrorMessage(remaining, false, - KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); - if (mRemainingAttemptsDialog == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext()); - builder.setMessage(msg); - builder.setCancelable(false); - builder.setNeutralButton(R.string.ok, null); - mRemainingAttemptsDialog = builder.create(); - mRemainingAttemptsDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } else { - mRemainingAttemptsDialog.setMessage(msg); - } - return mRemainingAttemptsDialog; - } - - @Override - public void onPause() { - // dismiss the dialog. - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.dismiss(); - mSimUnlockProgressDialog = null; - } - } - - /** - * Since the IPC can block, we want to run the request in a separate thread - * with a callback. - */ - private abstract class CheckSimPuk extends Thread { - - private final String mPin, mPuk; - private final int mSubId; - - protected CheckSimPuk(String puk, String pin, int subId) { - mPuk = puk; - mPin = pin; - mSubId = subId; - } - - abstract void onSimLockChangedResponse(@NonNull PinResult result); - - @Override - public void run() { - if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); - TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); - final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); - if (result == null) { - Log.e(TAG, "Error result for supplyPukReportResult."); - mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult())); - } else { - if (DEBUG) { - Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); - } - mView.post(new Runnable() { - @Override - public void run() { - onSimLockChangedResponse(result); - } - }); - } - } - } - -} diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java index 425e50ed6397..e59602b1cfff 100644 --- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java +++ b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java @@ -16,12 +16,11 @@ package com.android.keyguard; +import android.content.Context; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityManager; -import javax.inject.Inject; - /** * Hover listener that implements lift-to-activate interaction for * accessibility. May be added to multiple views. @@ -32,9 +31,9 @@ class LiftToActivateListener implements View.OnHoverListener { private boolean mCachedClickableState; - @Inject - LiftToActivateListener(AccessibilityManager accessibilityManager) { - mAccessibilityManager = accessibilityManager; + public LiftToActivateListener(Context context) { + mAccessibilityManager = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index 2205fdd4267d..b0457fce6a1a 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -26,7 +26,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; @@ -91,8 +90,7 @@ public class NumPadKey extends ViewGroup { } setOnClickListener(mListener); - setOnHoverListener(new LiftToActivateListener( - (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE))); + setOnHoverListener(new LiftToActivateListener(context)); mLockPatternUtils = new LockPatternUtils(context); mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java index 881108858b51..b6010c8915e7 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java @@ -22,7 +22,6 @@ import android.view.ViewGroup; import com.android.keyguard.KeyguardHostView; import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardSecurityContainer; -import com.android.keyguard.KeyguardSecurityViewFlipper; import com.android.systemui.R; import com.android.systemui.statusbar.phone.KeyguardBouncer; @@ -59,15 +58,7 @@ public interface KeyguardBouncerModule { /** */ @Provides @KeyguardBouncerScope - static KeyguardSecurityContainer providesKeyguardSecurityContainer(KeyguardHostView hostView) { + static KeyguardSecurityContainer preovidesKeyguardSecurityContainer(KeyguardHostView hostView) { return hostView.findViewById(R.id.keyguard_security_container); } - - /** */ - @Provides - @KeyguardBouncerScope - static KeyguardSecurityViewFlipper providesKeyguardSecurityViewFlipper( - KeyguardSecurityContainer containerView) { - return containerView.findViewById(R.id.view_flipper); - } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index eb431274b8a3..38e12a6ed5f8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -288,7 +288,6 @@ public class DependencyProvider { /** */ @Provides - @SysUISingleton public LockPatternUtils provideLockPatternUtils(Context context) { return new LockPatternUtils(context); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 79925bad3cc7..b35579d3624b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -62,7 +62,6 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; -import android.view.inputmethod.InputMethodManager; import com.android.internal.app.IBatteryStats; import com.android.internal.statusbar.IStatusBarService; @@ -184,12 +183,6 @@ public class FrameworkServicesModule { @Provides @Singleton - static InputMethodManager provideInputMethodManager(Context context) { - return context.getSystemService(InputMethodManager.class); - } - - @Provides - @Singleton static IPackageManager provideIPackageManager() { return IPackageManager.Stub.asInterface(ServiceManager.getService("package")); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index f150381f4070..636f42089743 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -274,7 +274,7 @@ class MediaCarouselController @Inject constructor( } } - private fun removePlayer(key: String) { + private fun removePlayer(key: String, dismissMediaData: Boolean = true) { val removed = MediaPlayerData.removeMediaPlayer(key) removed?.apply { mediaCarouselScrollHandler.onPrePlayerRemoved(removed) @@ -283,13 +283,16 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler.onPlayersChanged() updatePageIndicator() - // Inform the media manager of a potentially late dismissal - mediaManager.dismissMediaData(key, 0L) + if (dismissMediaData) { + // Inform the media manager of a potentially late dismissal + mediaManager.dismissMediaData(key, 0L) + } } } private fun recreatePlayers() { MediaPlayerData.mediaData().forEach { (key, data) -> + removePlayer(key, dismissMediaData = false) addOrUpdatePlayer(key = key, oldKey = null, data = data) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 9fc64d51cdf7..9b6a9ea80ebe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -42,7 +42,7 @@ import java.util.List; public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final String TAG = "MediaOutputAdapter"; - private static final int PAIR_NEW = 1; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public MediaOutputAdapter(MediaOutputController controller) { super(controller); @@ -58,11 +58,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @Override public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) { - if (mController.isZeroMode() && position == (mController.getMediaDevices().size())) { - viewHolder.onBind(PAIR_NEW); - } else if (position < (mController.getMediaDevices().size())) { - viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position)); - } else { + final int size = mController.getMediaDevices().size(); + if (mController.isZeroMode() && position == size) { + viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */, + true /* bottomMargin */); + } else if (position < size) { + viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position), + position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */); + } else if (DEBUG) { Log.d(TAG, "Incorrect position: " + position); } } @@ -83,7 +86,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } void onItemClick(int customizedItem) { - if (customizedItem == PAIR_NEW) { + if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { mController.launchBluetoothPairing(); } } @@ -112,51 +115,49 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } @Override - void onBind(MediaDevice device) { - super.onBind(device); + void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) { + super.onBind(device, topMargin, bottomMargin); if (mController.isTransferring()) { if (device.getState() == MediaDeviceState.STATE_CONNECTING && !mController.hasAdjustVolumeUserRestriction()) { - setTwoLineLayout(device, true); - mProgressBar.setVisibility(View.VISIBLE); - mSeekBar.setVisibility(View.GONE); - mSubTitleText.setVisibility(View.GONE); + setTwoLineLayout(device, null /* title */, true /* bFocused */, + false /* showSeekBar*/, true /* showProgressBar */, + false /* showSubtitle */); } else { - setSingleLineLayout(getItemTitle(device), false); + setSingleLineLayout(getItemTitle(device), false /* bFocused */); } } else { // Set different layout for each device if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { - setTwoLineLayout(device, false); - mSubTitleText.setVisibility(View.VISIBLE); - mSeekBar.setVisibility(View.GONE); - mProgressBar.setVisibility(View.GONE); + setTwoLineLayout(device, null /* title */, false /* bFocused */, + false /* showSeekBar*/, false /* showProgressBar */, + true /* showSubtitle */); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); mFrameLayout.setOnClickListener(v -> onItemClick(device)); } else if (!mController.hasAdjustVolumeUserRestriction() && isCurrentConnected(device)) { - setTwoLineLayout(device, true); - mSeekBar.setVisibility(View.VISIBLE); - mProgressBar.setVisibility(View.GONE); - mSubTitleText.setVisibility(View.GONE); + setTwoLineLayout(device, null /* title */, true /* bFocused */, + true /* showSeekBar*/, false /* showProgressBar */, + false /* showSubtitle */); initSeekbar(device); } else { - setSingleLineLayout(getItemTitle(device), false); + setSingleLineLayout(getItemTitle(device), false /* bFocused */); mFrameLayout.setOnClickListener(v -> onItemClick(device)); } } } @Override - void onBind(int customizedItem) { - if (customizedItem == PAIR_NEW) { + void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) { + super.onBind(customizedItem, topMargin, bottomMargin); + if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new), - false); + false /* bFocused */); final Drawable d = mContext.getDrawable(R.drawable.ic_add); d.setColorFilter(new PorterDuffColorFilter( Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN)); mTitleIcon.setImageDrawable(d); - mFrameLayout.setOnClickListener(v -> onItemClick(PAIR_NEW)); + mFrameLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 7579c25b030a..01dc6c4b71da 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -44,9 +44,12 @@ public abstract class MediaOutputBaseAdapter extends private static final String FONT_SELECTED_TITLE = "sans-serif-medium"; private static final String FONT_TITLE = "sans-serif"; + static final int CUSTOMIZED_ITEM_PAIR_NEW = 1; + final MediaOutputController mController; private boolean mIsDragging; + private int mMargin; Context mContext; View mHolderView; @@ -60,6 +63,8 @@ public abstract class MediaOutputBaseAdapter extends public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { mContext = viewGroup.getContext(); + mMargin = mContext.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_list_margin); mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item, viewGroup, false); @@ -106,12 +111,26 @@ public abstract class MediaOutputBaseAdapter extends mSeekBar = view.requireViewById(R.id.volume_seekbar); } - void onBind(MediaDevice device) { + void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) { mTitleIcon.setImageIcon(mController.getDeviceIconCompat(device).toIcon(mContext)); + setMargin(topMargin, bottomMargin); } - void onBind(int customizedItem) { } + void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) { + setMargin(topMargin, bottomMargin); + } + private void setMargin(boolean topMargin, boolean bottomMargin) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mFrameLayout + .getLayoutParams(); + if (topMargin) { + params.topMargin = mMargin; + } + if (bottomMargin) { + params.bottomMargin = mMargin; + } + mFrameLayout.setLayoutParams(params); + } void setSingleLineLayout(CharSequence title, boolean bFocused) { mTitleText.setVisibility(View.VISIBLE); mTwoLineLayout.setVisibility(View.GONE); @@ -123,10 +142,19 @@ public abstract class MediaOutputBaseAdapter extends } } - void setTwoLineLayout(MediaDevice device, boolean bFocused) { + void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused, + boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) { mTitleText.setVisibility(View.GONE); mTwoLineLayout.setVisibility(View.VISIBLE); - mTwoLineTitleText.setText(getItemTitle(device)); + mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); + mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); + mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); + if (device == null) { + mTwoLineTitleText.setText(title); + } else { + mTwoLineTitleText.setText(getItemTitle(device)); + } + if (bFocused) { mTwoLineTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL)); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index f8f4f4df58bc..ebca8a735ad5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -33,7 +33,6 @@ import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.widget.Button; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -69,12 +68,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private LinearLayout mDeviceListLayout; private Button mDoneButton; private Button mStopButton; - private View mListBottomPadding; private int mListMaxHeight; MediaOutputBaseAdapter mAdapter; - FrameLayout mGroupItemController; - View mGroupDivider; private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> { // Set max height for list @@ -114,12 +110,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle); mHeaderIcon = mDialogView.requireViewById(R.id.header_icon); mDevicesRecyclerView = mDialogView.requireViewById(R.id.list_result); - mGroupItemController = mDialogView.requireViewById(R.id.group_item_controller); - mGroupDivider = mDialogView.requireViewById(R.id.group_item_divider); mDeviceListLayout = mDialogView.requireViewById(R.id.device_list); mDoneButton = mDialogView.requireViewById(R.id.done); mStopButton = mDialogView.requireViewById(R.id.stop); - mListBottomPadding = mDialogView.requireViewById(R.id.list_bottom_padding); mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener( mDeviceListLayoutListener); @@ -162,7 +155,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } if (mHeaderIcon.getVisibility() == View.VISIBLE) { final int size = getHeaderIconSize(); - mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size, size)); + final int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_header_icon_padding); + mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); } // Update title and subtitle mHeaderTitle.setText(getHeaderText()); @@ -178,12 +173,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements if (!mAdapter.isDragging()) { mAdapter.notifyDataSetChanged(); } - // Add extra padding when device amount is less than 6 - if (mMediaOutputController.getMediaDevices().size() < 6) { - mListBottomPadding.setVisibility(View.VISIBLE); - } else { - mListBottomPadding.setVisibility(View.GONE); - } + // Show when remote media session is available + mStopButton.setVisibility(getStopButtonVisibility()); } abstract int getHeaderIconRes(); @@ -196,6 +187,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements abstract CharSequence getHeaderSubtitle(); + abstract int getStopButtonVisibility(); + @Override public void onMediaChanged() { mMainThreadHandler.post(() -> refresh()); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 64d20a273931..b1f1bda25961 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -24,6 +24,7 @@ import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.MediaMetadata; +import android.media.MediaRoute2Info; import android.media.RoutingSessionInfo; import android.media.session.MediaController; import android.media.session.MediaSessionManager; @@ -63,7 +64,7 @@ import javax.inject.Inject; public class MediaOutputController implements LocalMediaManager.DeviceCallback{ private static final String TAG = "MediaOutputController"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final String mPackageName; private final Context mContext; @@ -406,6 +407,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{ mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true); } + boolean isActiveRemoteDevice(@NonNull MediaDevice device) { + final List<String> features = device.getFeatures(); + return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK) + || features.contains(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK) + || features.contains(MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK) + || features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK)); + } + private final MediaController.Callback mCb = new MediaController.Callback() { @Override public void onMetadataChanged(MediaMetadata metadata) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index ac9d8ce52d88..a892a12f387b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -45,8 +45,6 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mGroupItemController.setVisibility(View.GONE); - mGroupDivider.setVisibility(View.GONE); } @Override @@ -74,4 +72,10 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { CharSequence getHeaderSubtitle() { return mMediaOutputController.getHeaderSubTitle(); } + + @Override + int getStopButtonVisibility() { + return mMediaOutputController.isActiveRemoteDevice( + mMediaOutputController.getCurrentConnectedMediaDevice()) ? View.VISIBLE : View.GONE; + } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 5b07db6c91a1..a3913aa9db93 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -254,7 +254,9 @@ public class PipMenuActivityController { if (isMenuVisible()) { // If the menu is visible in either the closed or full state, then hide the menu and // trigger the animation trigger afterwards - onStartCallback.run(); + if (onStartCallback != null) { + onStartCallback.run(); + } mPipMenuView.hideMenu(onEndCallback); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java index c66f442c4c0d..1c38ab338969 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java @@ -316,7 +316,7 @@ public class PipMenuView extends FrameLayout { } void hideMenu(Runnable animationEndCallback) { - hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false); + hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */); } private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, @@ -394,8 +394,10 @@ public class PipMenuView extends FrameLayout { // TODO: Check if the action drawable has changed before we reload it action.getIcon().loadDrawableAsync(mContext, d -> { - d.setTint(Color.WHITE); - actionView.setImageDrawable(d); + if (d != null) { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + } }, mHandler); actionView.setContentDescription(action.getContentDescription()); if (action.isEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java index a6cd350b33ce..eb8f065149c8 100644 --- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -23,6 +23,7 @@ import android.view.InflateException; import android.view.LayoutInflater; import android.view.View; +import com.android.keyguard.KeyguardMessageArea; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.qs.QSFooterImpl; import com.android.systemui.qs.QSPanel; @@ -107,6 +108,11 @@ public class InjectionInflationController { NotificationStackScrollLayout createNotificationStackScrollLayout(); /** + * Creates the KeyguardMessageArea. + */ + KeyguardMessageArea createKeyguardMessageArea(); + + /** * Creates the QSPanel. */ QSPanel createQSPanel(); diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java index c7aa780fcacb..64f8dbbb9e34 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java +++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java @@ -23,20 +23,7 @@ import android.view.View.OnAttachStateChangeListener; * Utility class that handles view lifecycle events for View Controllers. * * Implementations should handle setup and teardown related activities inside of - * {@link #onViewAttached()} and {@link #onViewDetached()}. Be sure to call {@link #init()} on - * any child controllers that this uses. This can be done in {@link init()} if the controllers - * are injected, or right after creation time of the child controller. - * - * Tip: View "attachment" happens top down - parents are notified that they are attached before - * any children. That means that if you call a method on a child controller in - * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method - * called, so it may not be fully set up. - * - * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()} - * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call - * {@link View#findViewById(int)} on its root view to setup member variables, do so in its - * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding - * listeners, dynamically changing content, or other runtime decisions. + * {@link #onViewAttached()} and {@link #onViewDetached()}. * * @param <T> View class that this ViewController is for. */ @@ -67,12 +54,10 @@ public abstract class ViewController<T extends View> { } mInited = true; - if (mView != null) { - if (mView.isAttachedToWindow()) { - mOnAttachStateListener.onViewAttachedToWindow(mView); - } - mView.addOnAttachStateChangeListener(mOnAttachStateListener); + if (mView.isAttachedToWindow()) { + mOnAttachStateListener.onViewAttachedToWindow(mView); } + mView.addOnAttachStateChangeListener(mOnAttachStateListener); } /** diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index a2d6ac8f8511..a3512bec6605 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -86,6 +86,7 @@ public final class WMShell extends SystemUI private final ProtoTracer mProtoTracer; private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback; + private KeyguardUpdateMonitorCallback mPipKeyguardCallback; private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback; @Inject @@ -140,6 +141,15 @@ public final class WMShell extends SystemUI pip.showPictureInPictureMenu(); } }); + mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onKeyguardVisibilityChanged(boolean showing) { + if (showing) { + pip.hidePipMenu(null, null); + } + } + }; + mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback); } @VisibleForTesting diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index dffad6ccbea5..9be2d124026c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -41,6 +41,8 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.SurfaceControlViewHost; import android.view.SurfaceView; +import android.view.ViewGroup; +import android.widget.FrameLayout; import androidx.test.filters.SmallTest; @@ -65,7 +67,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private ComponentName mComponentName; private Intent mServiceIntent; private TestableLooper mTestableLooper; - private KeyguardSecurityContainer mKeyguardSecurityContainer; + private ViewGroup mParent; @Mock private Handler mHandler; @@ -82,8 +84,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext)); - ViewUtils.attachView(mKeyguardSecurityContainer); + mParent = spy(new FrameLayout(mContext)); + ViewUtils.attachView(mParent); mTestableLooper = TestableLooper.get(this); mComponentName = new ComponentName(mContext, "FakeKeyguardClient.class"); @@ -94,14 +96,13 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient); when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient); - mTestController = new AdminSecondaryLockScreenController.Factory( - mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler) - .create(mKeyguardCallback); + mTestController = new AdminSecondaryLockScreenController( + mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler); } @After public void tearDown() { - ViewUtils.detachView(mKeyguardSecurityContainer); + ViewUtils.detachView(mParent); } @Test @@ -145,7 +146,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { SurfaceView v = verifySurfaceReady(); mTestController.hide(); - verify(mKeyguardSecurityContainer).removeView(v); + verify(mParent).removeView(v); assertThat(mContext.isBound(mComponentName)).isFalse(); } @@ -153,7 +154,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { public void testHide_notShown() throws Exception { mTestController.hide(); // Nothing should happen if trying to hide when the view isn't attached yet. - verify(mKeyguardSecurityContainer, never()).removeView(any(SurfaceView.class)); + verify(mParent, never()).removeView(any(SurfaceView.class)); } @Test @@ -181,7 +182,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private SurfaceView verifySurfaceReady() throws Exception { mTestableLooper.processAllMessages(); ArgumentCaptor<SurfaceView> captor = ArgumentCaptor.forClass(SurfaceView.class); - verify(mKeyguardSecurityContainer).addView(captor.capture()); + verify(mParent).addView(captor.capture()); mTestableLooper.processAllMessages(); verify(mKeyguardClient).onCreateKeyguardSurface(any(), any(IKeyguardCallback.class)); @@ -189,7 +190,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { } private void verifyViewDismissed(SurfaceView v) throws Exception { - verify(mKeyguardSecurityContainer).removeView(v); + verify(mParent).removeView(v); verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true); assertThat(mContext.isBound(mComponentName)).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java deleted file mode 100644 index c2ade81a9877..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.KeyEvent; - -import androidx.test.filters.SmallTest; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { - - @Mock - private KeyguardAbsKeyInputView mAbsKeyInputView; - @Mock - private PasswordTextView mPasswordEntry; - @Mock - private KeyguardMessageArea mKeyguardMessageArea; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private SecurityMode mSecurityMode; - @Mock - private LockPatternUtils mLockPatternUtils; - @Mock - private KeyguardSecurityCallback mKeyguardSecurityCallback; - @Mock - private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; - @Mock - private KeyguardMessageAreaController mKeyguardMessageAreaController; - @Mock - private LatencyTracker mLatencyTracker; - - private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) - .thenReturn(mKeyguardMessageAreaController); - when(mAbsKeyInputView.getPasswordTextViewId()).thenReturn(1); - when(mAbsKeyInputView.findViewById(1)).thenReturn(mPasswordEntry); - when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true); - when(mAbsKeyInputView.findViewById(R.id.keyguard_message_area)) - .thenReturn(mKeyguardMessageArea); - mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView, - mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mKeyguardMessageAreaControllerFactory, mLatencyTracker) { - @Override - void resetState() { - } - - @Override - public void onResume(int reason) { - super.onResume(reason); - } - }; - mKeyguardAbsKeyInputViewController.init(); - reset(mKeyguardMessageAreaController); // Clear out implicit call to init. - } - - @Test - public void onKeyDown_clearsSecurityMessage() { - ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor = - ArgumentCaptor.forClass(KeyDownListener.class); - verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture()); - onKeyDownListenerArgumentCaptor.getValue().onKeyDown( - KeyEvent.KEYCODE_0, mock(KeyEvent.class)); - verify(mKeyguardSecurityCallback).userActivity(); - verify(mKeyguardMessageAreaController).setMessage(eq("")); - } - - @Test - public void onKeyDown_noSecurityMessageInteraction() { - ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor = - ArgumentCaptor.forClass(KeyDownListener.class); - verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture()); - onKeyDownListenerArgumentCaptor.getValue().onKeyDown( - KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class)); - verifyZeroInteractions(mKeyguardSecurityCallback); - verifyZeroInteractions(mKeyguardMessageAreaController); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index e7930795c7f8..5999e2cdec78 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -41,9 +41,11 @@ import android.widget.FrameLayout; import android.widget.TextClock; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Test; @@ -76,7 +78,12 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) .thenReturn(mMockKeyguardSliceView); - LayoutInflater layoutInflater = LayoutInflater.from(getContext()); + InjectionInflationController inflationController = new InjectionInflationController( + SystemUIFactory.getInstance() + .getSysUIComponent() + .createViewInstanceCreatorFactory()); + LayoutInflater layoutInflater = inflationController + .injectable(LayoutInflater.from(getContext())); layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() { @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java deleted file mode 100644 index a7197cca530c..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; - -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class KeyguardMessageAreaControllerTest extends SysuiTestCase { - @Mock - private ConfigurationController mConfigurationController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private KeyguardMessageArea mKeyguardMessageArea; - - private KeyguardMessageAreaController mMessageAreaController; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mMessageAreaController = new KeyguardMessageAreaController.Factory( - mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea); - } - - @Test - public void onAttachedToWindow_registersConfigurationCallback() { - ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor = - ArgumentCaptor.forClass(ConfigurationListener.class); - - mMessageAreaController.onViewAttached(); - verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); - - mMessageAreaController.onViewDetached(); - verify(mConfigurationController).removeCallback( - eq(configurationListenerArgumentCaptor.getValue())); - } - - @Test - public void onAttachedToWindow_registersKeyguardUpdateMontiorCallback() { - ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor = - ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); - - mMessageAreaController.onViewAttached(); - verify(mKeyguardUpdateMonitor).registerCallback( - keyguardUpdateMonitorCallbackArgumentCaptor.capture()); - - mMessageAreaController.onViewDetached(); - verify(mKeyguardUpdateMonitor).removeCallback( - eq(keyguardUpdateMonitorCallbackArgumentCaptor.getValue())); - } - - @Test - public void testClearsTextField() { - mMessageAreaController.setMessage(""); - verify(mKeyguardMessageArea).setMessage(""); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java index 31fb25a7a89c..fc7b9a4b47d1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,60 +11,65 @@ * 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. + * limitations under the License */ package com.android.keyguard; -import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import android.view.View; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class KeyguardMessageAreaTest extends SysuiTestCase { - private KeyguardMessageArea mKeyguardMessageArea; + @Mock + private ConfigurationController mConfigurationController; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private KeyguardMessageArea mMessageArea; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mKeyguardMessageArea = new KeyguardMessageArea(mContext, null); - mKeyguardMessageArea.setBouncerVisible(true); + mMessageArea = new KeyguardMessageArea(mContext, null, mKeyguardUpdateMonitor, + mConfigurationController); + waitForIdleSync(); } @Test - public void testShowsTextField() { - mKeyguardMessageArea.setVisibility(View.INVISIBLE); - mKeyguardMessageArea.setMessage("oobleck"); - assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck"); - } + public void onAttachedToWindow_registersConfigurationCallback() { + mMessageArea.onAttachedToWindow(); + verify(mConfigurationController).addCallback(eq(mMessageArea)); - @Test - public void testHiddenWhenBouncerHidden() { - mKeyguardMessageArea.setBouncerVisible(false); - mKeyguardMessageArea.setVisibility(View.INVISIBLE); - mKeyguardMessageArea.setMessage("oobleck"); - assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); - assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck"); + mMessageArea.onDetachedFromWindow(); + verify(mConfigurationController).removeCallback(eq(mMessageArea)); } @Test - public void testClearsTextField() { - mKeyguardMessageArea.setVisibility(View.VISIBLE); - mKeyguardMessageArea.setMessage(""); - assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); - assertThat(mKeyguardMessageArea.getText()).isEqualTo(""); + public void clearFollowedByMessage_keepsMessage() { + mMessageArea.setMessage(""); + mMessageArea.setMessage("test"); + + CharSequence[] messageText = new CharSequence[1]; + messageText[0] = mMessageArea.getText(); + + assertEquals("test", messageText[0]); } + } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt deleted file mode 100644 index c69ec1a254c3..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard - -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import androidx.test.filters.SmallTest -import com.android.internal.util.LatencyTracker -import com.android.internal.widget.LockPatternUtils -import com.android.internal.widget.LockPatternView -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class KeyguardPatternViewControllerTest : SysuiTestCase() { - @Mock - private lateinit var mKeyguardPatternView: KeyguardPatternView - @Mock - private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock - private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode - @Mock - private lateinit var mLockPatternUtils: LockPatternUtils - @Mock - private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback - @Mock - private lateinit var mLatencyTracker: LatencyTracker - @Mock - private lateinit - var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory - @Mock - private lateinit var mKeyguardMessageArea: KeyguardMessageArea - @Mock - private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController - @Mock - private lateinit var mLockPatternView: LockPatternView - - private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true) - `when`(mKeyguardPatternView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area)) - .thenReturn(mKeyguardMessageArea) - `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView)) - .thenReturn(mLockPatternView) - `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea)) - .thenReturn(mKeyguardMessageAreaController) - mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView, - mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mLatencyTracker, mKeyguardMessageAreaControllerFactory) - } - - @Test - fun onPause_clearsTextField() { - mKeyguardPatternViewController.init() - mKeyguardPatternViewController.onPause() - verify(mKeyguardMessageAreaController).setMessage("") - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt new file mode 100644 index 000000000000..b4363cf215f1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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.keyguard + +import androidx.test.filters.SmallTest +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.LayoutInflater + +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.policy.ConfigurationController +import com.google.common.truth.Truth.assertThat + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardPatternViewTest : SysuiTestCase() { + + private lateinit var mKeyguardPatternView: KeyguardPatternView + private lateinit var mSecurityMessage: KeyguardMessageArea + + @Before + fun setup() { + val inflater = LayoutInflater.from(context) + mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java) + mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null) + as KeyguardPatternView + mSecurityMessage = KeyguardMessageArea(mContext, null, + mock(KeyguardUpdateMonitor::class.java), mock(ConfigurationController::class.java)) + mKeyguardPatternView.mSecurityMessageDisplay = mSecurityMessage + } + + @Test + fun onPause_clearsTextField() { + mSecurityMessage.setMessage("an old message") + mKeyguardPatternView.onPause() + assertThat(mSecurityMessage.text).isEqualTo("") + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java deleted file mode 100644 index 4944284698a0..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { - - @Mock - private KeyguardPinBasedInputView mPinBasedInputView; - @Mock - private PasswordTextView mPasswordEntry; - @Mock - private KeyguardMessageArea mKeyguardMessageArea; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private SecurityMode mSecurityMode; - @Mock - private LockPatternUtils mLockPatternUtils; - @Mock - private KeyguardSecurityCallback mKeyguardSecurityCallback; - @Mock - private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; - @Mock - private KeyguardMessageAreaController mKeyguardMessageAreaController; - @Mock - private LatencyTracker mLatencyTracker; - @Mock - private LiftToActivateListener mLiftToactivateListener; - @Mock - private View mDeleteButton; - @Mock - private View mOkButton; - - private KeyguardPinBasedInputViewController mKeyguardPinViewController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) - .thenReturn(mKeyguardMessageAreaController); - when(mPinBasedInputView.getPasswordTextViewId()).thenReturn(1); - when(mPinBasedInputView.findViewById(1)).thenReturn(mPasswordEntry); - when(mPinBasedInputView.isAttachedToWindow()).thenReturn(true); - when(mPinBasedInputView.findViewById(R.id.keyguard_message_area)) - .thenReturn(mKeyguardMessageArea); - when(mPinBasedInputView.findViewById(R.id.delete_button)) - .thenReturn(mDeleteButton); - when(mPinBasedInputView.findViewById(R.id.key_enter)) - .thenReturn(mOkButton); - mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, - mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) { - @Override - public void onResume(int reason) { - super.onResume(reason); - } - }; - mKeyguardPinViewController.init(); - } - - @Test - public void onResume_requestsFocus() { - mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); - verify(mPasswordEntry).requestFocus(); - } -} - diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java new file mode 100644 index 000000000000..6666a926c68b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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.keyguard; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.KeyEvent; +import android.view.LayoutInflater; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class KeyguardPinBasedInputViewTest extends SysuiTestCase { + + @Mock + private PasswordTextView mPasswordEntry; + @Mock + private SecurityMessageDisplay mSecurityMessageDisplay; + @InjectMocks + private KeyguardPinBasedInputView mKeyguardPinView; + + @Before + public void setup() { + LayoutInflater inflater = LayoutInflater.from(getContext()); + mDependency.injectMockDependency(KeyguardUpdateMonitor.class); + mKeyguardPinView = + (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null); + MockitoAnnotations.initMocks(this); + } + + @Test + public void onResume_requestsFocus() { + mKeyguardPinView.onResume(KeyguardSecurityView.SCREEN_ON); + verify(mPasswordEntry).requestFocus(); + } + + @Test + public void onKeyDown_clearsSecurityMessage() { + mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_0, mock(KeyEvent.class)); + verify(mSecurityMessageDisplay).setMessage(eq("")); + } + + @Test + public void onKeyDown_noSecurityMessageInteraction() { + mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class)); + verifyZeroInteractions(mSecurityMessageDisplay); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java index ae159c73b99f..559284ac0672 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java @@ -31,7 +31,9 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.InjectionInflationController; import org.junit.After; import org.junit.Before; @@ -63,6 +65,7 @@ public class KeyguardPresentationTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mDependency.injectMockDependency(KeyguardUpdateMonitor.class); when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardStatusView.getContext()).thenReturn(mContext); @@ -74,7 +77,11 @@ public class KeyguardPresentationTest extends SysuiTestCase { allowTestableLooperAsMainThread(); - mLayoutInflater = LayoutInflater.from(mContext); + InjectionInflationController inflationController = new InjectionInflationController( + SystemUIFactory.getInstance() + .getSysUIComponent() + .createViewInstanceCreatorFactory()); + mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext)); mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() { @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java deleted file mode 100644 index cdb91ecfad89..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.WindowInsetsController; - -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper() -public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { - - @Rule - public MockitoRule mRule = MockitoJUnit.rule(); - - @Mock - private KeyguardSecurityContainer mView; - @Mock - private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory; - @Mock - private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; - @Mock - private LockPatternUtils mLockPatternUtils; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private KeyguardSecurityModel mKeyguardSecurityModel; - @Mock - private MetricsLogger mMetricsLogger; - @Mock - private UiEventLogger mUiEventLogger; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardInputViewController mInputViewController; - @Mock - private KeyguardSecurityContainer.SecurityCallback mSecurityCallback; - @Mock - private WindowInsetsController mWindowInsetsController; - @Mock - private KeyguardSecurityViewFlipper mSecurityViewFlipper; - @Mock - private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; - - private KeyguardSecurityContainerController mKeyguardSecurityContainerController; - - @Before - public void setup() { - when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class))) - .thenReturn(mAdminSecondaryLockScreenController); - when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - - mKeyguardSecurityContainerController = new KeyguardSecurityContainerController( - mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, - mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, - mKeyguardStateController, mKeyguardSecurityViewFlipperController); - - mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback); - } - - @Test - public void showSecurityScreen_canInflateAllModes() { - SecurityMode[] modes = SecurityMode.values(); - for (SecurityMode mode : modes) { - when(mInputViewController.getSecurityMode()).thenReturn(mode); - mKeyguardSecurityContainerController.showSecurityScreen(mode); - if (mode == SecurityMode.Invalid) { - verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView( - any(SecurityMode.class), any(KeyguardSecurityCallback.class)); - } else { - verify(mKeyguardSecurityViewFlipperController).getSecurityView( - eq(mode), any(KeyguardSecurityCallback.class)); - } - } - } - - @Test - public void startDisappearAnimation_animatesKeyboard() { - when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( - SecurityMode.Password); - when(mInputViewController.getSecurityMode()).thenReturn( - SecurityMode.Password); - when(mKeyguardSecurityViewFlipperController.getSecurityView( - eq(SecurityMode.Password), any(KeyguardSecurityCallback.class))) - .thenReturn(mInputViewController); - mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */); - - mKeyguardSecurityContainerController.startDisappearAnimation(null); - verify(mInputViewController).startDisappearAnimation(eq(null)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 854be1f76722..a867825e223d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -19,19 +19,23 @@ package com.android.keyguard; import static android.view.WindowInsets.Type.ime; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.LayoutInflater; import android.view.WindowInsetsController; import androidx.test.filters.SmallTest; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Rule; @@ -46,26 +50,68 @@ import org.mockito.junit.MockitoRule; @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerTest extends SysuiTestCase { - @Rule - public MockitoRule mRule = MockitoJUnit.rule(); - + @Mock + private KeyguardSecurityModel mKeyguardSecurityModel; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private KeyguardSecurityContainer.SecurityCallback mSecurityCallback; + @Mock + private KeyguardSecurityView mSecurityView; @Mock private WindowInsetsController mWindowInsetsController; @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; - + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); private KeyguardSecurityContainer mKeyguardSecurityContainer; @Before public void setup() { - when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); + mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController); + mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel); + mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor); + mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()) { + @Override + protected KeyguardSecurityView getSecurityView( + KeyguardSecurityModel.SecurityMode securityMode) { + return mSecurityView; + } + }; mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; + when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); + mKeyguardSecurityContainer.setSecurityCallback(mSecurityCallback); + } + + @Test + public void showSecurityScreen_canInflateAllModes() { + Context context = getContext(); + + for (int theme : new int[] {R.style.Theme_SystemUI, R.style.Theme_SystemUI_Light}) { + context.setTheme(theme); + final LayoutInflater inflater = LayoutInflater.from(context); + KeyguardSecurityModel.SecurityMode[] modes = + KeyguardSecurityModel.SecurityMode.values(); + for (KeyguardSecurityModel.SecurityMode mode : modes) { + final int resId = mKeyguardSecurityContainer.getLayoutIdFor(mode); + if (resId == 0) { + continue; + } + inflater.inflate(resId, null /* root */, false /* attach */); + } + } } @Test public void startDisappearAnimation_animatesKeyboard() { - mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password); + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.Password); + mKeyguardSecurityContainer.showPrimarySecurityScreen(false /* turningOff */); + + mKeyguardSecurityContainer.startDisappearAnimation(null); + verify(mSecurityView).startDisappearAnimation(eq(null)); verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(), any(), any()); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java deleted file mode 100644 index 3b7f4b839853..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.view.WindowInsetsController; - -import androidx.test.filters.SmallTest; - -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper() -public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { - - @Rule - public MockitoRule mRule = MockitoJUnit.rule(); - - @Mock - private KeyguardSecurityViewFlipper mView; - @Mock - private LayoutInflater mLayoutInflater; - @Mock - private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory; - @Mock - private KeyguardInputViewController mKeyguardInputViewController; - @Mock - private KeyguardInputView mInputView; - @Mock - private WindowInsetsController mWindowInsetsController; - @Mock - private KeyguardSecurityCallback mKeyguardSecurityCallback; - - private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; - - @Before - public void setup() { - when(mKeyguardSecurityViewControllerFactory.create( - any(KeyguardInputView.class), any(SecurityMode.class), - any(KeyguardSecurityCallback.class))) - .thenReturn(mKeyguardInputViewController); - when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController); - - mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView, - mLayoutInflater, mKeyguardSecurityViewControllerFactory); - } - - @Test - public void showSecurityScreen_canInflateAllModes() { - SecurityMode[] modes = SecurityMode.values(); - // Always return an invalid controller so that we're always making a new one. - when(mKeyguardInputViewController.getSecurityMode()).thenReturn(SecurityMode.Invalid); - for (SecurityMode mode : modes) { - reset(mLayoutInflater); - when(mLayoutInflater.inflate(anyInt(), eq(mView), eq(false))) - .thenReturn(mInputView); - mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback); - if (mode == SecurityMode.Invalid || mode == SecurityMode.None) { - verify(mLayoutInflater, never()).inflate( - anyInt(), any(ViewGroup.class), anyBoolean()); - } else { - verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false)); - } - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java index 79ec4f2c553a..0431704778c3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java @@ -24,7 +24,9 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Test; @@ -48,7 +50,13 @@ public class KeyguardStatusViewTest extends SysuiTestCase { @Before public void setUp() { allowTestableLooperAsMainThread(); - LayoutInflater layoutInflater = LayoutInflater.from(getContext()); + mDependency.injectMockDependency(KeyguardUpdateMonitor.class); + InjectionInflationController inflationController = new InjectionInflationController( + SystemUIFactory.getInstance() + .getSysUIComponent() + .createViewInstanceCreatorFactory()); + LayoutInflater layoutInflater = inflationController + .injectable(LayoutInflater.from(getContext())); mKeyguardStatusView = (KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null); org.mockito.MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 42b21c61510a..27b5b7fda684 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -36,7 +36,6 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.media.MediaDevice; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; @@ -55,7 +54,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { // Mock private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class); - private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); private ShadeController mShadeController = mock(ShadeController.class); @@ -157,30 +155,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { verify(mMediaOutputBaseAdapter).notifyDataSetChanged(); } - @Test - public void refresh_with6Devices_checkBottomPaddingVisibility() { - for (int i = 0; i < 6; i++) { - mMediaOutputController.mMediaDevices.add(mock(MediaDevice.class)); - } - mMediaOutputBaseDialogImpl.refresh(); - final View view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById( - R.id.list_bottom_padding); - - assertThat(view.getVisibility()).isEqualTo(View.GONE); - } - - @Test - public void refresh_with5Devices_checkBottomPaddingVisibility() { - for (int i = 0; i < 5; i++) { - mMediaOutputController.mMediaDevices.add(mock(MediaDevice.class)); - } - mMediaOutputBaseDialogImpl.refresh(); - final View view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById( - R.id.list_bottom_padding); - - assertThat(view.getVisibility()).isEqualTo(View.VISIBLE); - } - class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog { MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) { @@ -189,24 +163,34 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mAdapter = mMediaOutputBaseAdapter; } + @Override int getHeaderIconRes() { return mHeaderIconRes; } + @Override IconCompat getHeaderIcon() { return mIconCompat; } + @Override int getHeaderIconSize() { return 10; } + @Override CharSequence getHeaderText() { return mHeaderTitle; } + @Override CharSequence getHeaderSubtitle() { return mHeaderSubtitle; } + + @Override + int getStopButtonVisibility() { + return 0; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java new file mode 100644 index 000000000000..ca328fbe44fb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.media.MediaRoute2Info; +import android.media.session.MediaSessionManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.media.LocalMediaManager; +import com.android.settingslib.media.MediaDevice; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.phone.ShadeController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class MediaOutputDialogTest extends SysuiTestCase { + + private static final String TEST_PACKAGE = "test_package"; + + // Mock + private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); + private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); + private ShadeController mShadeController = mock(ShadeController.class); + private ActivityStarter mStarter = mock(ActivityStarter.class); + private LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class); + private MediaDevice mMediaDevice = mock(MediaDevice.class); + + private MediaOutputDialog mMediaOutputDialog; + private MediaOutputController mMediaOutputController; + private List<String> mFeatures = new ArrayList<>(); + + @Before + public void setUp() { + mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, + mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter); + mMediaOutputController.mLocalMediaManager = mLocalMediaManager; + mMediaOutputDialog = new MediaOutputDialog(mContext, false, mMediaOutputController); + + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); + when(mMediaDevice.getFeatures()).thenReturn(mFeatures); + } + + @Test + public void getStopButtonVisibility_remoteDevice_returnVisible() { + mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK); + + assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE); + + mFeatures.clear(); + mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK); + + assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE); + + mFeatures.clear(); + mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK); + + assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE); + + mFeatures.clear(); + mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK); + + assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void getStopButtonVisibility_localDevice_returnGone() { + mFeatures.add(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK); + + assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.GONE); + } + +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 9495fb5fdc77..ff794691d2b4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -730,7 +730,8 @@ public class AccessibilityWindowManager { case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY: - case WindowManager.LayoutParams.TYPE_SCREENSHOT: { + case WindowManager.LayoutParams.TYPE_SCREENSHOT: + case WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY: { return AccessibilityWindowInfo.TYPE_SYSTEM; } diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java index b7fed87d570d..958c15c8d432 100644 --- a/services/core/java/android/os/BatteryStatsInternal.java +++ b/services/core/java/android/os/BatteryStatsInternal.java @@ -50,5 +50,10 @@ public abstract class BatteryStatsInternal { * Informs battery stats of binder stats for the given work source UID. */ public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount, - Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids); + Collection<BinderCallsStats.CallStat> callStats); + + /** + * Informs battery stats of native thread IDs of threads taking incoming binder calls. + */ + public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids); } diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java index c9513592ea79..66ac889ff2ca 100644 --- a/services/core/java/com/android/server/BinderCallsStatsService.java +++ b/services/core/java/com/android/server/BinderCallsStatsService.java @@ -49,6 +49,7 @@ import com.android.internal.util.DumpUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public class BinderCallsStatsService extends Binder { @@ -273,7 +274,19 @@ public class BinderCallsStatsService extends Binder { BatteryStatsInternal batteryStatsInternal = getLocalService( BatteryStatsInternal.class); - mBinderCallsStats.setCallStatsObserver(batteryStatsInternal::noteBinderCallStats); + mBinderCallsStats.setCallStatsObserver(new BinderInternal.CallStatsObserver() { + @Override + public void noteCallStats(int workSourceUid, long incrementalCallCount, + Collection<BinderCallsStats.CallStat> callStats) { + batteryStatsInternal.noteBinderCallStats(workSourceUid, + incrementalCallCount, callStats); + } + + @Override + public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) { + batteryStatsInternal.noteBinderThreadNativeIds(binderThreadNativeTids); + } + }); // It needs to be called before mService.systemReady to make sure the observer is // initialized before installing it. diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index e342c1fcf114..6d774869b391 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -4641,6 +4641,11 @@ class StorageManagerService extends IStorageManager.Stub // make sure the OBB dir for the application is setup correctly, if it exists. File[] packageObbDirs = userEnv.buildExternalStorageAppObbDirs(packageName); for (File packageObbDir : packageObbDirs) { + if (packageObbDir.getPath().startsWith( + Environment.getDataPreloadsMediaDirectory().getPath())) { + Slog.i(TAG, "Skipping app data preparation for " + packageObbDir); + continue; + } try { mVold.fixupAppDir(packageObbDir.getCanonicalPath() + "/", uid); } catch (IOException e) { diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index bcb7bfacfaf3..0135b9d2f8ab 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -72,7 +72,6 @@ import com.android.internal.app.DisableCarModeActivity; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.DumpUtils; -import com.android.server.SystemService.TargetUser; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; @@ -327,8 +326,16 @@ final class UiModeManagerService extends SystemService { @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { mCurrentUser = to.getUserIdentifier(); + if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier()); getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver); verifySetupWizardCompleted(); + synchronized (mLock) { + // only update if the value is actually changed + if (updateNightModeFromSettingsLocked(getContext(), getContext().getResources(), + to.getUserIdentifier())) { + updateLocked(0, 0); + } + } } @Override @@ -356,11 +363,10 @@ final class UiModeManagerService extends SystemService { new IntentFilter(Intent.ACTION_DOCK_EVENT)); IntentFilter batteryFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); context.registerReceiver(mBatteryReceiver, batteryFilter); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); context.registerReceiver(mSettingsRestored, new IntentFilter(Intent.ACTION_SETTING_RESTORED)); - context.registerReceiver(new UserSwitchedReceiver(), filter, null, mHandler); + context.registerReceiver(mOnShutdown, + new IntentFilter(Intent.ACTION_SHUTDOWN)); updateConfigurationLocked(); applyConfigurationExternallyLocked(); } @@ -407,6 +413,21 @@ final class UiModeManagerService extends SystemService { publishLocalService(UiModeManagerInternal.class, mLocalService); } + private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mNightMode == MODE_NIGHT_AUTO) { + persistComputedNightMode(mCurrentUser); + } + } + }; + + private void persistComputedNightMode(int userId) { + Secure.putIntForUser(getContext().getContentResolver(), + Secure.UI_NIGHT_MODE_LAST_COMPUTED, mComputedNightMode ? 1 : 0, + userId); + } + private final BroadcastReceiver mSettingsRestored = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -508,6 +529,10 @@ final class UiModeManagerService extends SystemService { Secure.getLongForUser(context.getContentResolver(), Secure.DARK_THEME_CUSTOM_END_TIME, DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000); + if (mNightMode == MODE_NIGHT_AUTO) { + mComputedNightMode = Secure.getIntForUser(context.getContentResolver(), + Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0; + } } return oldNightMode != mNightMode; @@ -1630,18 +1655,4 @@ final class UiModeManagerService extends SystemService { } } } - - private final class UserSwitchedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - final int currentId = intent.getIntExtra( - Intent.EXTRA_USER_HANDLE, USER_SYSTEM); - // only update if the value is actually changed - if (updateNightModeFromSettingsLocked(context, context.getResources(), currentId)) { - updateLocked(0, 0); - } - } - } - } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index bfe04e628f81..c0f6011a45cd 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -482,13 +482,14 @@ public final class ActiveServices { String callingPackage, @Nullable String callingFeatureId, final int userId) throws TransactionTooLargeException { return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired, - hideFgNotification, callingPackage, callingFeatureId, userId, false); + hideFgNotification, callingPackage, callingFeatureId, userId, false, null); } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification, String callingPackage, @Nullable String callingFeatureId, final int userId, - boolean allowBackgroundActivityStarts) throws TransactionTooLargeException { + boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken) + throws TransactionTooLargeException { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); @@ -733,7 +734,7 @@ public final class ActiveServices { } } if (allowBackgroundActivityStarts) { - r.allowBgActivityStartsOnServiceStart(); + r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken); } ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); return cmp; @@ -2609,12 +2610,12 @@ public final class ActiveServices { private int getAllowMode(Intent service, @Nullable String callingPackage) { if (callingPackage == null || service.getComponent() == null) { - return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL; + return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; } if (callingPackage.equals(service.getComponent().getPackageName())) { - return ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL; + return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; } else { - return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL; + return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2de3e525d2da..bd7d3de9d200 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16306,7 +16306,8 @@ public class ActivityManagerService extends IActivityManager.Stub try { res = mServices.startServiceLocked(null, service, resolvedType, -1, uid, fgRequired, false, callingPackage, - callingFeatureId, userId, allowBackgroundActivityStarts); + callingFeatureId, userId, allowBackgroundActivityStarts, + backgroundActivityStartsToken); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 808a6993af1a..76125aabc978 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -240,14 +240,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override public void noteBinderCallStats(int workSourceUid, long incrementatCallCount, - Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) { + Collection<BinderCallsStats.CallStat> callStats) { synchronized (BatteryStatsService.this.mLock) { mHandler.sendMessage(PooledLambda.obtainMessage( - mStats::noteBinderCallStats, workSourceUid, incrementatCallCount, - callStats, binderThreadNativeTids, + mStats::noteBinderCallStats, workSourceUid, incrementatCallCount, callStats, SystemClock.elapsedRealtime(), SystemClock.uptimeMillis())); } } + + @Override + public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) { + synchronized (BatteryStatsService.this.mLock) { + mStats.noteBinderThreadNativeIds(binderThreadNativeTids); + } + } } @Override diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 93105daa6c5d..57f811215e50 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1686,7 +1686,7 @@ public final class BroadcastQueue { // that request - we don't want the token to be swept from under our feet... mHandler.removeCallbacksAndMessages(msgToken); // ...then add the token - proc.addAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken); + proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken); } final void setBroadcastTimeoutLocked(long timeoutTime) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index f1e12c55df18..2dced8d704bb 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -77,6 +77,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; /** @@ -1338,13 +1339,15 @@ class ProcessRecord implements WindowProcessListener { * {@param originatingToken} if you have one such originating token, this is useful for tracing * back the grant in the case of the notification token. */ - void addAllowBackgroundActivityStartsToken(Binder entity, @Nullable IBinder originatingToken) { - if (entity == null) return; - mWindowProcessController.addAllowBackgroundActivityStartsToken(entity, originatingToken); + void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity, + @Nullable IBinder originatingToken) { + Objects.requireNonNull(entity); + mWindowProcessController.addOrUpdateAllowBackgroundActivityStartsToken(entity, + originatingToken); } void removeAllowBackgroundActivityStartsToken(Binder entity) { - if (entity == null) return; + Objects.requireNonNull(entity); mWindowProcessController.removeAllowBackgroundActivityStartsToken(entity); } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 55e0f2ea5212..a7d8ca496f75 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -22,6 +22,7 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.ComponentName; @@ -139,6 +140,12 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // used to clean up the state of mIsAllowedBgActivityStartsByStart after a timeout private Runnable mCleanUpAllowBgActivityStartsByStartCallback; private ProcessRecord mAppForAllowingBgActivityStartsByStart; + // These are the originating tokens that currently allow bg activity starts by service start. + // This is used to trace back the grant when starting activities. We only pass such token to the + // ProcessRecord if it's the *only* cause for bg activity starts exemption, otherwise we pass + // null. + @GuardedBy("ams") + private List<IBinder> mBgActivityStartsByStartOriginatingTokens = new ArrayList<>(); // allow while-in-use permissions in foreground service or not. // while-in-use permissions in FGS started from background might be restricted. @@ -588,7 +595,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ? _proc : null; if (mIsAllowedBgActivityStartsByStart || mIsAllowedBgActivityStartsByBinding) { - _proc.addAllowBackgroundActivityStartsToken(this, null); + _proc.addOrUpdateAllowBackgroundActivityStartsToken(this, + getExclusiveOriginatingToken()); } else { _proc.removeAllowBackgroundActivityStartsToken(this); } @@ -679,10 +687,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } void setAllowedBgActivityStartsByBinding(boolean newValue) { - if (mIsAllowedBgActivityStartsByBinding != newValue) { - mIsAllowedBgActivityStartsByBinding = newValue; - updateParentProcessBgActivityStartsToken(); - } + mIsAllowedBgActivityStartsByBinding = newValue; + updateParentProcessBgActivityStartsToken(); } /** @@ -691,7 +697,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN * timeout. Note that the ability for starting background activities persists for the process * even if the service is subsequently stopped. */ - void allowBgActivityStartsOnServiceStart() { + void allowBgActivityStartsOnServiceStart(@Nullable IBinder originatingToken) { + mBgActivityStartsByStartOriginatingTokens.add(originatingToken); setAllowedBgActivityStartsByStart(true); if (app != null) { mAppForAllowingBgActivityStartsByStart = app; @@ -701,32 +708,49 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (mCleanUpAllowBgActivityStartsByStartCallback == null) { mCleanUpAllowBgActivityStartsByStartCallback = () -> { synchronized (ams) { - if (app == mAppForAllowingBgActivityStartsByStart) { - // The process we allowed is still running the service. We remove - // the ability by start, but it may still be allowed via bound connections. - setAllowedBgActivityStartsByStart(false); - } else if (mAppForAllowingBgActivityStartsByStart != null) { - // The process we allowed is not running the service. It therefore can't be - // bound so we can unconditionally remove the ability. - mAppForAllowingBgActivityStartsByStart - .removeAllowBackgroundActivityStartsToken(ServiceRecord.this); + mBgActivityStartsByStartOriginatingTokens.remove(0); + if (!mBgActivityStartsByStartOriginatingTokens.isEmpty()) { + // There are other callbacks in the queue, let's just update the originating + // token + if (mIsAllowedBgActivityStartsByStart) { + mAppForAllowingBgActivityStartsByStart + .addOrUpdateAllowBackgroundActivityStartsToken( + this, getExclusiveOriginatingToken()); + } else { + Slog.wtf(TAG, + "Service callback to revoke bg activity starts by service " + + "start triggered but " + + "mIsAllowedBgActivityStartsByStart = false. This " + + "should never happen."); + } + } else { + // Last callback on the queue + if (app == mAppForAllowingBgActivityStartsByStart) { + // The process we allowed is still running the service. We remove + // the ability by start, but it may still be allowed via bound + // connections. + setAllowedBgActivityStartsByStart(false); + } else if (mAppForAllowingBgActivityStartsByStart != null) { + // The process we allowed is not running the service. It therefore can't + // be bound so we can unconditionally remove the ability. + mAppForAllowingBgActivityStartsByStart + .removeAllowBackgroundActivityStartsToken(ServiceRecord.this); + } + mAppForAllowingBgActivityStartsByStart = null; } - mAppForAllowingBgActivityStartsByStart = null; } }; } - // if there's a request pending from the past, drop it before scheduling a new one - ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback); + // Existing callbacks will only update the originating token, only when the last callback is + // executed is the grant revoked. ams.mHandler.postDelayed(mCleanUpAllowBgActivityStartsByStartCallback, ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT); } private void setAllowedBgActivityStartsByStart(boolean newValue) { - if (mIsAllowedBgActivityStartsByStart != newValue) { - mIsAllowedBgActivityStartsByStart = newValue; - updateParentProcessBgActivityStartsToken(); - } + mIsAllowedBgActivityStartsByStart = newValue; + updateParentProcessBgActivityStartsToken(); } /** @@ -736,8 +760,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN * {@code mIsAllowedBgActivityStartsByBinding}. If either is true, this ServiceRecord * should be contributing as a token in parent ProcessRecord. * - * @see com.android.server.am.ProcessRecord#addAllowBackgroundActivityStartsToken(Binder, - * IBinder) + * @see com.android.server.am.ProcessRecord#addOrUpdateAllowBackgroundActivityStartsToken( + * Binder, IBinder) * @see com.android.server.am.ProcessRecord#removeAllowBackgroundActivityStartsToken(Binder) */ private void updateParentProcessBgActivityStartsToken() { @@ -747,12 +771,37 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (mIsAllowedBgActivityStartsByStart || mIsAllowedBgActivityStartsByBinding) { // if the token is already there it's safe to "re-add it" - we're dealing with // a set of Binder objects - app.addAllowBackgroundActivityStartsToken(this, null); + app.addOrUpdateAllowBackgroundActivityStartsToken(this, getExclusiveOriginatingToken()); } else { app.removeAllowBackgroundActivityStartsToken(this); } } + /** + * Returns the originating token if that's the only reason background activity starts are + * allowed. In order for that to happen the service has to be allowed only due to starts, since + * bindings are not associated with originating tokens, and all the start tokens have to be the + * same and there can't be any null originating token in the queue. + * + * Originating tokens are optional, so the caller could provide null when it allows bg activity + * starts. + */ + @Nullable + private IBinder getExclusiveOriginatingToken() { + if (mIsAllowedBgActivityStartsByBinding + || mBgActivityStartsByStartOriginatingTokens.isEmpty()) { + return null; + } + IBinder firstToken = mBgActivityStartsByStartOriginatingTokens.get(0); + for (int i = 1, n = mBgActivityStartsByStartOriginatingTokens.size(); i < n; i++) { + IBinder token = mBgActivityStartsByStartOriginatingTokens.get(i); + if (token != firstToken) { + return null; + } + } + return firstToken; + } + @GuardedBy("ams") void updateKeepWarmLocked() { mKeepWarming = ams.mConstants.KEEP_WARMING_SERVICES.contains(name) diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 3dfbcc71dd3c..eb60573e6f17 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -23,11 +23,10 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; -import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL; -import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL; +import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; -import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; @@ -1912,12 +1911,11 @@ class UserController implements Handler.Callback { callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) { // If the caller does not have either permission, they are always doomed. allow = false; - } else if (allowMode == ALLOW_NON_FULL - || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) { + } else if (allowMode == ALLOW_NON_FULL) { // We are blanket allowing non-full access, you lucky caller! allow = true; - } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE_OR_FULL - || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL) { + } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE + || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { // We may or may not allow this depending on whether the two users are // in the same profile. allow = isSameProfileGroup; @@ -1944,15 +1942,12 @@ class UserController implements Handler.Callback { builder.append("; this requires "); builder.append(INTERACT_ACROSS_USERS_FULL); if (allowMode != ALLOW_FULL_ONLY) { - if (allowMode == ALLOW_NON_FULL - || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL - || isSameProfileGroup) { + if (allowMode == ALLOW_NON_FULL || isSameProfileGroup) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); } if (isSameProfileGroup - && (allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL - || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL)) { + && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { builder.append(" or "); builder.append(INTERACT_ACROSS_PROFILES); } @@ -1979,8 +1974,7 @@ class UserController implements Handler.Callback { private boolean canInteractWithAcrossProfilesPermission( int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid, String callingPackage) { - if (allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL - && allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) { + if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { return false; } if (!isSameProfileGroup) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 6099e52e54e9..668713f0fee7 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -19,7 +19,6 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; -import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL; import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; @@ -37,7 +36,6 @@ import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; -import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; @@ -131,7 +129,6 @@ import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.EventLog; import android.util.KeyValueListParser; import android.util.LongSparseArray; import android.util.Pair; @@ -165,7 +162,6 @@ import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemServiceManager; -import com.android.server.am.ActivityManagerService; import com.android.server.pm.PackageList; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -2203,11 +2199,8 @@ public class AppOpsService extends IAppOpsService.Stub { + " by uid " + Binder.getCallingUid()); } - int userId = UserHandle.getUserId(uid); - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); verifyIncomingOp(code); - verifyIncomingUser(userId); code = AppOpsManager.opToSwitch(code); if (permissionPolicyCallback == null) { @@ -2252,11 +2245,6 @@ public class AppOpsService extends IAppOpsService.Stub { scheduleWriteLocked(); } uidState.evalForegroundOps(mOpModeWatchers); - - if (code == OP_INTERACT_ACROSS_PROFILES) { - // Invalidate package info cache as the visibility of packages might have changed - PackageManager.invalidatePackageInfoCache(); - } } notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback); @@ -2462,12 +2450,8 @@ public class AppOpsService extends IAppOpsService.Stub { private void setMode(int code, int uid, @NonNull String packageName, int mode, @Nullable IAppOpsCallback permissionPolicyCallback) { enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); - - int userId = UserHandle.getUserId(uid); - verifyIncomingOp(code); - verifyIncomingUser(userId); - verifyIncomingPackage(packageName, userId); + verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); ArraySet<ModeCallback> repCbs = null; code = AppOpsManager.opToSwitch(code); @@ -2729,9 +2713,6 @@ public class AppOpsService extends IAppOpsService.Stub { if (changed) { scheduleFastWriteLocked(); - - // Invalidate package info cache as the visibility of packages might have changed - PackageManager.invalidatePackageInfoCache(); } } if (callbacks != null) { @@ -2890,11 +2871,8 @@ public class AppOpsService extends IAppOpsService.Stub { private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { - int userId = UserHandle.getUserId(uid); - verifyIncomingOp(code); - verifyIncomingUser(userId); - verifyIncomingPackage(packageName, userId); + verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3013,15 +2991,10 @@ public class AppOpsService extends IAppOpsService.Stub { String proxiedAttributionTag, int proxyUid, String proxyPackageName, String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { - int proxiedUserId = UserHandle.getUserId(proxiedUid); - int proxyUserId = UserHandle.getUserId(proxyUid); - verifyIncomingUid(proxyUid); verifyIncomingOp(code); - verifyIncomingUser(proxiedUserId); - verifyIncomingUser(proxyUserId); - verifyIncomingPackage(proxiedPackageName, proxiedUserId); - verifyIncomingPackage(proxyPackageName, proxyUserId); + verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid)); + verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid)); String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName); if (resolveProxyPackageName == null) { @@ -3071,12 +3044,9 @@ public class AppOpsService extends IAppOpsService.Stub { private int noteOperationImpl(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { - int userId = UserHandle.getUserId(uid); - verifyIncomingUid(uid); verifyIncomingOp(code); - verifyIncomingUser(userId); - verifyIncomingPackage(packageName, userId); + verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3453,12 +3423,9 @@ public class AppOpsService extends IAppOpsService.Stub { public int startOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { - int userId = UserHandle.getUserId(uid); - verifyIncomingUid(uid); verifyIncomingOp(code); - verifyIncomingUser(userId); - verifyIncomingPackage(packageName, userId); + verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3550,12 +3517,9 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag) { - int userId = UserHandle.getUserId(uid); - verifyIncomingUid(uid); verifyIncomingOp(code); - verifyIncomingUser(userId); - verifyIncomingPackage(packageName, userId); + verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3784,33 +3748,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private void verifyIncomingUser(@UserIdInt int userId) { - int callingUid = Binder.getCallingUid(); - int callingUserId = UserHandle.getUserId(callingUid); - int callingPid = Binder.getCallingPid(); - - if (callingUserId != userId) { - // Prevent endless loop between when checking appops inside of handleIncomingUser - if (Binder.getCallingPid() == ActivityManagerService.MY_PID) { - return; - } - long token = Binder.clearCallingIdentity(); - try { - try { - LocalServices.getService(ActivityManagerInternal.class).handleIncomingUser( - callingPid, callingUid, userId, /* allowAll */ false, - ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL, "appop operation", null); - } catch (Exception e) { - EventLog.writeEvent(0x534e4554, "153996875", "appop", userId); - - throw e; - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - private @Nullable UidState getUidStateLocked(int uid, boolean edit) { UidState uidState = mUidStates.get(uid); if (uidState == null) { @@ -5890,11 +5827,8 @@ public class AppOpsService extends IAppOpsService.Stub { return false; } } - int userId = UserHandle.getUserId(uid); - verifyIncomingOp(code); - verifyIncomingUser(userId); - verifyIncomingPackage(packageName, userId); + verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); final String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index a3e1b7a7e5c5..84de25c06ebf 100644 --- a/services/core/java/com/android/server/appop/TEST_MAPPING +++ b/services/core/java/com/android/server/appop/TEST_MAPPING @@ -7,9 +7,6 @@ "name": "CtsAppOps2TestCases" }, { - "name": "CtsAppOpHostTestCases" - }, - { "name": "FrameworksServicesTests", "options": [ { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6110759b5a9e..f1561cab8fb4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6735,14 +6735,17 @@ public class AudioService extends IAudioService.Stub if (msg.obj == null) { break; } - // If the app corresponding to this mode death handler object is not - // capturing or playing audio anymore after 3 seconds, remove it - // from the stack. Otherwise, check again in 3 seconds. + // If no other app is currently owning the audio mode and + // the app corresponding to this mode death handler object is still in the + // mode owner stack but not capturing or playing audio after 3 seconds, + // remove it from the stack. + // Otherwise, check again in 3 seconds. SetModeDeathHandler h = (SetModeDeathHandler) msg.obj; if (mSetModeDeathHandlers.indexOf(h) < 0) { break; } - if (mRecordMonitor.isRecordingActiveForUid(h.getUid()) + if (getModeOwnerUid() != h.getUid() + || mRecordMonitor.isRecordingActiveForUid(h.getUid()) || mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())) { sendMsg(mAudioHandler, MSG_CHECK_MODE_FOR_UID, diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index ded0f9a3dca7..8de31d999b53 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -450,6 +450,7 @@ public class BtHelper { } @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + // @GuardedBy("BtHelper.this") private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource)); client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 0b4f31d365d3..28bd97e4843c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -117,14 +117,15 @@ public class HdmiCecMessageValidator { // TODO: Validate more than length for the following messages. // Messages for the One Touch Record. - FixedLengthValidator oneByteValidator = new FixedLengthValidator(1); addValidationInfo(Constants.MESSAGE_RECORD_ON, new VariableLengthValidator(1, 8), DEST_DIRECT); - addValidationInfo(Constants.MESSAGE_RECORD_STATUS, oneByteValidator, DEST_DIRECT); + addValidationInfo(Constants.MESSAGE_RECORD_STATUS, + new RecordStatusInfoValidator(), DEST_DIRECT); // TODO: Handle messages for the Timer Programming. // Messages for the System Information. + FixedLengthValidator oneByteValidator = new FixedLengthValidator(1); addValidationInfo(Constants.MESSAGE_CEC_VERSION, oneByteValidator, DEST_DIRECT); addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE, new FixedLengthValidator(3), DEST_BROADCAST); @@ -339,4 +340,23 @@ public class HdmiCecMessageValidator { isValidPhysicalAddress(params, 0) && isValidPhysicalAddress(params, 2)); } } + + /** + * Check if the given record status message parameter is valid. + * A valid parameter should lie within the range description of Record Status Info defined in + * CEC 1.4 Specification : Operand Descriptions (Section 17) + */ + private class RecordStatusInfoValidator implements ParameterValidator { + @Override + public int isValid(byte[] params) { + if (params.length < 1) { + return ERROR_PARAMETER_SHORT; + } + return toErrorCode(isWithinRange(params[0], 0x01, 0x07) + || isWithinRange(params[0], 0x09, 0x0E) + || isWithinRange(params[0], 0x10, 0x17) + || isWithinRange(params[0], 0x1A, 0x1B) + || params[0] == 0x1F); + } + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7401403de127..1fd7a73f1174 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -309,6 +309,7 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.stream.Collectors; /** {@hide} */ public class NotificationManagerService extends SystemService { @@ -1528,10 +1529,10 @@ public class NotificationManagerService extends SystemService { cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0, !queryRestart, changeUserId, reason, null); } - } else if (hideNotifications) { - hideNotificationsForPackages(pkgList); - } else if (unhideNotifications) { - unhideNotificationsForPackages(pkgList); + } else if (hideNotifications && uidList != null && (uidList.length > 0)) { + hideNotificationsForPackages(pkgList, uidList); + } else if (unhideNotifications && uidList != null && (uidList.length > 0)) { + unhideNotificationsForPackages(pkgList, uidList); } } @@ -2076,6 +2077,68 @@ public class NotificationManagerService extends SystemService { mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource( com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos)); mStatsManager = statsManager; + + // register for various Intents. + // If this is called within a test, make sure to unregister the intent receivers by + // calling onDestroy() + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_STOPPED); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_USER_UNLOCKED); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); + + IntentFilter pkgFilter = new IntentFilter(); + pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + pkgFilter.addDataScheme("package"); + getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null, + null); + + IntentFilter suspendedPkgFilter = new IntentFilter(); + suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); + suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); + suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); + getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, + suspendedPkgFilter, null, null); + + IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null, + null); + + IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT); + timeoutFilter.addDataScheme(SCHEME_TIMEOUT); + getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter); + + IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED); + getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter); + + IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); + getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); + } + + /** + * Cleanup broadcast receivers change listeners. + */ + public void onDestroy() { + getContext().unregisterReceiver(mIntentReceiver); + getContext().unregisterReceiver(mPackageIntentReceiver); + getContext().unregisterReceiver(mNotificationTimeoutReceiver); + getContext().unregisterReceiver(mRestoreReceiver); + getContext().unregisterReceiver(mLocaleChangeReceiver); + + if (mDeviceConfigChangedListener != null) { + DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); + } } protected String[] getStringArrayResource(int key) { @@ -2126,51 +2189,6 @@ public class NotificationManagerService extends SystemService { Context.STATS_MANAGER), getContext().getSystemService(TelephonyManager.class)); - // register for various Intents - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - filter.addAction(Intent.ACTION_USER_PRESENT); - filter.addAction(Intent.ACTION_USER_STOPPED); - filter.addAction(Intent.ACTION_USER_SWITCHED); - filter.addAction(Intent.ACTION_USER_ADDED); - filter.addAction(Intent.ACTION_USER_REMOVED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); - - IntentFilter pkgFilter = new IntentFilter(); - pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); - pkgFilter.addDataScheme("package"); - getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null, - null); - - IntentFilter suspendedPkgFilter = new IntentFilter(); - suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); - suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); - suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); - getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, - suspendedPkgFilter, null, null); - - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null, - null); - - IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT); - timeoutFilter.addDataScheme(SCHEME_TIMEOUT); - getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter); - - IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED); - getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter); - - IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); - getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); - publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL); publishLocalService(NotificationManagerInternal.class, mInternalService); @@ -2193,12 +2211,6 @@ public class NotificationManagerService extends SystemService { mDeviceConfigChangedListener); } - void unregisterDeviceConfigChange() { - if (mDeviceConfigChangedListener != null) { - DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); - } - } - private void registerNotificationPreferencesPullers() { mPullAtomCallback = new StatsPullAtomCallbackImpl(); mStatsManager.setPullAtomCallback( @@ -8339,15 +8351,16 @@ public class NotificationManagerService extends SystemService { return -1; } - @VisibleForTesting - protected void hideNotificationsForPackages(String[] pkgs) { + private void hideNotificationsForPackages(@NonNull String[] pkgs, @NonNull int[] uidList) { synchronized (mNotificationLock) { + Set<Integer> uidSet = Arrays.stream(uidList).boxed().collect(Collectors.toSet()); List<String> pkgList = Arrays.asList(pkgs); List<NotificationRecord> changedNotifications = new ArrayList<>(); int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - if (pkgList.contains(rec.getSbn().getPackageName())) { + if (pkgList.contains(rec.getSbn().getPackageName()) + && uidSet.contains(rec.getUid())) { rec.setHidden(true); changedNotifications.add(rec); } @@ -8357,15 +8370,17 @@ public class NotificationManagerService extends SystemService { } } - @VisibleForTesting - protected void unhideNotificationsForPackages(String[] pkgs) { + private void unhideNotificationsForPackages(@NonNull String[] pkgs, + @NonNull int[] uidList) { synchronized (mNotificationLock) { + Set<Integer> uidSet = Arrays.stream(uidList).boxed().collect(Collectors.toSet()); List<String> pkgList = Arrays.asList(pkgs); List<NotificationRecord> changedNotifications = new ArrayList<>(); int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - if (pkgList.contains(rec.getSbn().getPackageName())) { + if (pkgList.contains(rec.getSbn().getPackageName()) + && uidSet.contains(rec.getUid())) { rec.setHidden(false); changedNotifications.add(rec); } @@ -9939,38 +9954,6 @@ public class NotificationManagerService extends SystemService { return CollectionUtils.firstOrNull(allowedComponents); } - @VisibleForTesting - protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) { - checkCallerIsSystemOrShell(); - // only use for testing: mimic receive broadcast that package is (un)suspended - // but does not actually (un)suspend the package - final Bundle extras = new Bundle(); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, - new String[]{pkg}); - - final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED - : Intent.ACTION_PACKAGES_UNSUSPENDED; - final Intent intent = new Intent(action); - intent.putExtras(extras); - - mPackageIntentReceiver.onReceive(getContext(), intent); - } - - @VisibleForTesting - protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) { - checkCallerIsSystemOrShell(); - // only use for testing: mimic receive broadcast that package is (un)distracting - // but does not actually register that info with packagemanager - final Bundle extras = new Bundle(); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs); - extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag); - - final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); - intent.putExtras(extras); - - mPackageIntentReceiver.onReceive(getContext(), intent); - } - /** * Wrapper for a StatusBarNotification object that allows transfer across a oneway * binder without sending large amounts of data over a oneway transaction. diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index da7864ba32c4..927dc25ac6ce 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -66,8 +66,6 @@ public class NotificationShellCmd extends ShellCommand { + " set_dnd [on|none (same as on)|priority|alarms|all|off (same as all)]" + " allow_dnd PACKAGE [user_id (current user if not specified)]\n" + " disallow_dnd PACKAGE [user_id (current user if not specified)]\n" - + " suspend_package PACKAGE\n" - + " unsuspend_package PACKAGE\n" + " reset_assistant_user_set [user_id (current user if not specified)]\n" + " get_approved_assistant [user_id (current user if not specified)]\n" + " post [--help | flags] TAG TEXT\n" @@ -258,25 +256,6 @@ public class NotificationShellCmd extends ShellCommand { mBinderService.setNotificationAssistantAccessGrantedForUser(cn, userId, false); } break; - case "suspend_package": { - // only use for testing - mDirectService.simulatePackageSuspendBroadcast(true, getNextArgRequired()); - } - break; - case "unsuspend_package": { - // only use for testing - mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired()); - } - break; - case "distract_package": { - // only use for testing - // Flag values are in - // {@link android.content.pm.PackageManager.DistractionRestriction}. - mDirectService.simulatePackageDistractionBroadcast( - Integer.parseInt(getNextArgRequired()), - getNextArgRequired().split(",")); - break; - } case "reset_assistant_user_set": { int userId = ActivityManager.getCurrentUser(); if (peekNextArg() != null) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index f52db5fb5f2f..31ee59717dba 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -618,6 +618,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 + && !isCalledBySystemOrShell(callingUid) + && (mPm.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) == 0) { + throw new SecurityException( + "Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag."); + } + if (params.isStaged && !isCalledBySystemOrShell(callingUid)) { if (mBypassNextStagedInstallerCheck) { mBypassNextStagedInstallerCheck = false; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 697c31a78ec7..f94de227f3e9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1849,7 +1849,6 @@ public class PackageManagerService extends IPackageManager.Stub Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mLock) { removeMessages(WRITE_PACKAGE_LIST); - mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.writePackageListLPr(msg.arg1); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); @@ -2686,7 +2685,8 @@ public class PackageManagerService extends IPackageManager.Stub (i, pm) -> new Settings(Environment.getDataDirectory(), i.getPermissionManagerServiceInternal().getPermissionSettings(), - RuntimePermissionsPersistence.createInstance(), lock), + RuntimePermissionsPersistence.createInstance(), + i.getPermissionManagerServiceInternal(), lock), new Injector.LocalServicesProducer<>(ActivityTaskManagerInternal.class), new Injector.LocalServicesProducer<>(ActivityManagerInternal.class), new Injector.LocalServicesProducer<>(DeviceIdleInternal.class), @@ -4486,8 +4486,8 @@ public class PackageManagerService extends IPackageManager.Stub AndroidPackage p = ps.pkg; if (p != null) { // Compute GIDs only if requested - final int[] gids = (flags & PackageManager.GET_GIDS) == 0 - ? EMPTY_INT_ARRAY : mPermissionManager.getPackageGids(ps.name, userId); + final int[] gids = (flags & PackageManager.GET_GIDS) == 0 ? EMPTY_INT_ARRAY + : mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId)); // Compute granted permissions only if package has requested permissions final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions()) ? Collections.emptySet() @@ -4962,13 +4962,13 @@ public class PackageManagerService extends IPackageManager.Stub } // TODO: Shouldn't this be checking for package installed state for userId and // return null? - return mPermissionManager.getPackageGids(packageName, userId); + return mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId)); } if ((flags & MATCH_KNOWN_PACKAGES) != 0) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null && ps.isMatch(flags) && !shouldFilterApplicationLocked(ps, callingUid, userId)) { - return mPermissionManager.getPackageGids(packageName, userId); + return mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId)); } } } @@ -18983,7 +18983,7 @@ public class PackageManagerService extends IPackageManager.Stub } if ((deletedPs.sharedUser == null || deletedPs.sharedUser.packages.size() == 0) && !isUpdatedSystemApp(deletedPs)) { - mPermissionManager.removePermissionsStateTEMP(removedAppId); + mPermissionManager.removeAppIdStateTEMP(removedAppId); } mPermissionManager.updatePermissions(deletedPs.name, null); if (deletedPs.sharedUser != null) { @@ -21242,7 +21242,8 @@ public class PackageManagerService extends IPackageManager.Stub // Prior to enabling the package, we need to decompress the APK(s) to the // data partition and then replace the version on the system partition. final AndroidPackage deletedPkg = pkgSetting.pkg; - final boolean isSystemStub = deletedPkg.isStub() + final boolean isSystemStub = (deletedPkg != null) + && deletedPkg.isStub() && deletedPkg.isSystem(); if (isSystemStub && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT @@ -21854,8 +21855,6 @@ public class PackageManagerService extends IPackageManager.Stub protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; - mPermissionManager.writeStateToPackageSettingsTEMP(); - DumpState dumpState = new DumpState(); boolean fullPreferred = false; boolean checkin = false; @@ -23734,7 +23733,6 @@ public class PackageManagerService extends IPackageManager.Stub mDirtyUsers.remove(userId); mUserNeedsBadging.delete(userId); mPermissionManager.onUserRemoved(userId); - mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.removeUserLPw(userId); mPendingBroadcasts.remove(userId); mInstantAppRegistry.onUserRemovedLPw(userId); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index d77683e8ba61..afbf7d3a35af 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2323,7 +2323,8 @@ class PackageManagerShellCommand extends ShellCommand { private boolean isVendorApp(String pkg) { try { - final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); + final PackageInfo info = mInterface.getPackageInfo( + pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM); return info != null && info.applicationInfo.isVendor(); } catch (RemoteException e) { return false; @@ -2332,7 +2333,8 @@ class PackageManagerShellCommand extends ShellCommand { private boolean isProductApp(String pkg) { try { - final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); + final PackageInfo info = mInterface.getPackageInfo( + pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM); return info != null && info.applicationInfo.isProduct(); } catch (RemoteException e) { return false; @@ -2341,7 +2343,8 @@ class PackageManagerShellCommand extends ShellCommand { private boolean isSystemExtApp(String pkg) { try { - final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); + final PackageInfo info = mInterface.getPackageInfo( + pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM); return info != null && info.applicationInfo.isSystemExt(); } catch (RemoteException e) { return false; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 276f88082df0..855a5ff524fd 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -28,7 +28,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.pm.permission.PermissionsState; +import com.android.server.pm.permission.AppIdPermissionState; import com.android.server.pm.pkg.PackageStateUnserialized; import java.io.File; @@ -215,7 +215,7 @@ public class PackageSetting extends PackageSettingBase { } @Override - public PermissionsState getPermissionsState() { + public AppIdPermissionState getPermissionsState() { return (sharedUser != null) ? sharedUser.getPermissionsState() : super.getPermissionsState(); diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index 3e2ab05e83ec..c1258b1efd48 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -19,23 +19,23 @@ package com.android.server.pm; import android.content.pm.ApplicationInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.permission.PermissionsState; +import com.android.server.pm.permission.AppIdPermissionState; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public abstract class SettingBase { int pkgFlags; int pkgPrivateFlags; - protected final PermissionsState mPermissionsState; + protected final AppIdPermissionState mPermissionsState; SettingBase(int pkgFlags, int pkgPrivateFlags) { setFlags(pkgFlags); setPrivateFlags(pkgPrivateFlags); - mPermissionsState = new PermissionsState(); + mPermissionsState = new AppIdPermissionState(); } SettingBase(SettingBase orig) { - mPermissionsState = new PermissionsState(); + mPermissionsState = new AppIdPermissionState(); doCopy(orig); } @@ -49,7 +49,7 @@ public abstract class SettingBase { mPermissionsState.copyFrom(orig.mPermissionsState); } - public PermissionsState getPermissionsState() { + public AppIdPermissionState getPermissionsState() { return mPermissionsState; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 965430727d3c..bae36b2ad353 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -79,6 +79,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.IntArray; import android.util.Log; import android.util.LogPrinter; import android.util.Pair; @@ -106,10 +107,11 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.permission.AppIdPermissionState; +import com.android.server.pm.permission.AppIdPermissionState.PermissionState; import com.android.server.pm.permission.BasePermission; +import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.PermissionSettings; -import com.android.server.pm.permission.PermissionsState; -import com.android.server.pm.permission.PermissionsState.PermissionState; import com.android.server.utils.TimingsTraceAndSlog; import libcore.io.IoUtils; @@ -416,9 +418,12 @@ public final class Settings { private final File mSystemDir; public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages); + /** Settings and other information about permissions */ final PermissionSettings mPermissions; + private final LegacyPermissionDataProvider mPermissionDataProvider; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public Settings(Map<String, PackageSetting> pkgSettings) { mLock = new Object(); @@ -426,6 +431,7 @@ public final class Settings { mSystemDir = null; mPermissions = null; mRuntimePermissionsPersistence = null; + mPermissionDataProvider = null; mSettingsFilename = null; mBackupSettingsFilename = null; mPackageListFilename = null; @@ -434,12 +440,14 @@ public final class Settings { mKernelMappingFilename = null; } - Settings(File dataDir, PermissionSettings permission, - RuntimePermissionsPersistence runtimePermissionsPersistence, Object lock) { + Settings(File dataDir, PermissionSettings permissionSettings, + RuntimePermissionsPersistence runtimePermissionsPersistence, + LegacyPermissionDataProvider permissionDataProvider, Object lock) { mLock = lock; - mPermissions = permission; + mPermissions = permissionSettings; mRuntimePermissionsPersistence = new RuntimePermissionPersistence( - runtimePermissionsPersistence, mLock); + runtimePermissionsPersistence); + mPermissionDataProvider = permissionDataProvider; mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); @@ -1239,7 +1247,7 @@ public final class Settings { void writeAllRuntimePermissionsLPr() { for (int userId : UserManagerService.getInstance().getUserIds()) { - mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); + mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId); } } @@ -2102,7 +2110,7 @@ public final class Settings { } void readInstallPermissionsLPr(XmlPullParser parser, - PermissionsState permissionsState) throws IOException, XmlPullParserException { + AppIdPermissionState permissionsState) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -2131,25 +2139,7 @@ public final class Settings { final int flags = (flagsStr != null) ? Integer.parseInt(flagsStr, 16) : 0; - if (granted) { - if (permissionsState.grantInstallPermission(bp) == - PermissionsState.PERMISSION_OPERATION_FAILURE) { - Slog.w(PackageManagerService.TAG, "Permission already added: " + name); - XmlUtils.skipCurrentTag(parser); - } else { - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); - } - } else { - if (permissionsState.revokeInstallPermission(bp) == - PermissionsState.PERMISSION_OPERATION_FAILURE) { - Slog.w(PackageManagerService.TAG, "Permission already added: " + name); - XmlUtils.skipCurrentTag(parser); - } else { - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); - } - } + permissionsState.putInstallPermissionState(new PermissionState(bp, granted, flags)); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: " + parser.getName()); @@ -2158,7 +2148,7 @@ public final class Settings { } } - void writePermissionsLPr(XmlSerializer serializer, List<PermissionState> permissionStates) + void writePermissionsLPr(XmlSerializer serializer, Collection<PermissionState> permissionStates) throws IOException { if (permissionStates.isEmpty()) { return; @@ -2641,7 +2631,11 @@ public final class Settings { } final boolean isDebug = pkg.pkg.isDebuggable(); - final int[] gids = pkg.getPermissionsState().computeGids(userIds); + final IntArray gids = new IntArray(); + for (final int userId : userIds) { + gids.addAll(mPermissionDataProvider.getGidsForUid(UserHandle.getUid(userId, + pkg.appId))); + } // Avoid any application that has a space in its path. if (dataPath.indexOf(' ') >= 0) @@ -2673,11 +2667,12 @@ public final class Settings { sb.append(" "); sb.append(AndroidPackageUtils.getSeInfo(pkg.pkg, pkg)); sb.append(" "); - if (gids != null && gids.length > 0) { - sb.append(gids[0]); - for (int i = 1; i < gids.length; i++) { + final int gidsSize = gids.size(); + if (gids != null && gids.size() > 0) { + sb.append(gids.get(0)); + for (int i = 1; i < gidsSize; i++) { sb.append(","); - sb.append(gids[i]); + sb.append(gids.get(i)); } } else { sb.append("none"); @@ -4482,8 +4477,9 @@ public final class Settings { } void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag, - ArraySet<String> permissionNames, PackageSetting ps, SimpleDateFormat sdf, - Date date, List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) { + ArraySet<String> permissionNames, PackageSetting ps, + AppIdPermissionState permissionsState, SimpleDateFormat sdf, Date date, + List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) { AndroidPackage pkg = ps.pkg; if (checkinTag != null) { pw.print(checkinTag); @@ -4810,7 +4806,6 @@ public final class Settings { } if (ps.sharedUser == null || permissionNames != null || dumpAll) { - PermissionsState permissionsState = ps.getPermissionsState(); dumpInstallPermissionsLPr(pw, prefix + " ", permissionNames, permissionsState); } @@ -4889,8 +4884,8 @@ public final class Settings { } if (ps.sharedUser == null) { - PermissionsState permissionsState = ps.getPermissionsState(); - dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id)); + dumpGidsLPr(pw, prefix + " ", mPermissionDataProvider.getGidsForUid( + UserHandle.getUid(user.id, ps.appId))); dumpRuntimePermissionsLPr(pw, prefix + " ", permissionNames, permissionsState .getRuntimePermissionStates(user.id), dumpAll); } @@ -4933,8 +4928,10 @@ public final class Settings { && !packageName.equals(ps.name)) { continue; } + final AppIdPermissionState permissionsState = + mPermissionDataProvider.getAppIdPermissionState(ps.appId); if (permissionNames != null - && !ps.getPermissionsState().hasRequestedPermission(permissionNames)) { + && !permissionsState.hasPermissionState(permissionNames)) { continue; } @@ -4948,8 +4945,8 @@ public final class Settings { pw.println("Packages:"); printedSomething = true; } - dumpPackageLPr(pw, " ", checkin ? "pkg" : null, permissionNames, ps, sdf, date, users, - packageName != null, dumpAllComponents); + dumpPackageLPr(pw, " ", checkin ? "pkg" : null, permissionNames, ps, permissionsState, + sdf, date, users, packageName != null, dumpAllComponents); } printedSomething = false; @@ -4989,8 +4986,10 @@ public final class Settings { pw.println("Hidden system packages:"); printedSomething = true; } - dumpPackageLPr(pw, " ", checkin ? "dis" : null, permissionNames, ps, sdf, date, - users, packageName != null, dumpAllComponents); + final AppIdPermissionState permissionsState = + mPermissionDataProvider.getAppIdPermissionState(ps.appId); + dumpPackageLPr(pw, " ", checkin ? "dis" : null, permissionNames, ps, + permissionsState, sdf, date, users, packageName != null, dumpAllComponents); } } } @@ -5018,8 +5017,10 @@ public final class Settings { if (packageName != null && su != dumpState.getSharedUser()) { continue; } + final AppIdPermissionState permissionsState = + mPermissionDataProvider.getAppIdPermissionState(su.userId); if (permissionNames != null - && !su.getPermissionsState().hasRequestedPermission(permissionNames)) { + && !permissionsState.hasPermissionState(permissionNames)) { continue; } if (!checkin) { @@ -5054,12 +5055,12 @@ public final class Settings { continue; } - final PermissionsState permissionsState = su.getPermissionsState(); dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState); for (int userId : UserManagerService.getInstance().getUserIds()) { - final int[] gids = permissionsState.computeGids(userId); - final List<PermissionState> permissions = + final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid( + userId, su.userId)); + final Collection<PermissionState> permissions = permissionsState.getRuntimePermissionStates(userId); if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) { pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": "); @@ -5120,7 +5121,7 @@ public final class Settings { } void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames, - List<PermissionState> permissionStates, boolean dumpAll) { + Collection<PermissionState> permissionStates, boolean dumpAll) { if (!permissionStates.isEmpty() || dumpAll) { pw.print(prefix); pw.println("runtime permissions:"); for (PermissionState permissionState : permissionStates) { @@ -5161,8 +5162,9 @@ public final class Settings { } void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames, - PermissionsState permissionsState) { - List<PermissionState> permissionStates = permissionsState.getInstallPermissionStates(); + AppIdPermissionState permissionsState) { + Collection<PermissionState> permissionStates = + permissionsState.getInstallPermissionStates(); if (!permissionStates.isEmpty()) { pw.print(prefix); pw.println("install permissions:"); for (PermissionState permissionState : permissionStates) { @@ -5202,9 +5204,9 @@ public final class Settings { public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { if (sync) { - mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId); + mRuntimePermissionsPersistence.writeStateForUserSyncLPr(userId); } else { - mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); + mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId); } } @@ -5292,8 +5294,6 @@ public final class Settings { private final Handler mHandler = new MyHandler(); - private final Object mPersistenceLock; - @GuardedBy("mLock") private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray(); @@ -5313,10 +5313,8 @@ public final class Settings { // The mapping keys are user ids. private final SparseBooleanArray mPermissionUpgradeNeeded = new SparseBooleanArray(); - public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence, - Object persistenceLock) { + public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence) { mPersistence = persistence; - mPersistenceLock = persistenceLock; } @GuardedBy("Settings.this.mLock") @@ -5327,7 +5325,7 @@ public final class Settings { @GuardedBy("Settings.this.mLock") void setVersionLPr(int version, int userId) { mVersions.put(userId, version); - writePermissionsForUserAsyncLPr(userId); + writeStateForUserAsyncLPr(userId); } @GuardedBy("Settings.this.mLock") @@ -5342,7 +5340,7 @@ public final class Settings { + "set before trying to update the fingerprint."); } mFingerprints.put(userId, mExtendedFingerprint); - writePermissionsForUserAsyncLPr(userId); + writeStateForUserAsyncLPr(userId); } public void setPermissionControllerVersion(long version) { @@ -5361,13 +5359,7 @@ public final class Settings { return Build.FINGERPRINT + "?pc_version=" + version; } - public void writePermissionsForUserSyncLPr(int userId) { - mHandler.removeMessages(userId); - writePermissionsSync(userId); - } - - @GuardedBy("Settings.this.mLock") - public void writePermissionsForUserAsyncLPr(int userId) { + public void writeStateForUserAsyncLPr(int userId) { final long currentTimeMillis = SystemClock.uptimeMillis(); if (mWriteScheduled.get(userId)) { @@ -5399,59 +5391,53 @@ public final class Settings { } } - private void writePermissionsSync(int userId) { - RuntimePermissionsState runtimePermissions; - synchronized (mPersistenceLock) { - mWriteScheduled.delete(userId); - - int version = mVersions.get(userId, INITIAL_VERSION); + public void writeStateForUserSyncLPr(int userId) { + mHandler.removeMessages(userId); + mWriteScheduled.delete(userId); - String fingerprint = mFingerprints.get(userId); + int version = mVersions.get(userId, INITIAL_VERSION); - Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = - new ArrayMap<>(); - int packagesSize = mPackages.size(); - for (int i = 0; i < packagesSize; i++) { - String packageName = mPackages.keyAt(i); - PackageSetting packageSetting = mPackages.valueAt(i); - if (packageSetting.sharedUser == null) { - List<RuntimePermissionsState.PermissionState> permissions = - getPermissionsFromPermissionsState( - packageSetting.getPermissionsState(), userId); - packagePermissions.put(packageName, permissions); - } - } + String fingerprint = mFingerprints.get(userId); - Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = - new ArrayMap<>(); - final int sharedUsersSize = mSharedUsers.size(); - for (int i = 0; i < sharedUsersSize; i++) { - String sharedUserName = mSharedUsers.keyAt(i); - SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i); + Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = + new ArrayMap<>(); + int packagesSize = mPackages.size(); + for (int i = 0; i < packagesSize; i++) { + String packageName = mPackages.keyAt(i); + PackageSetting packageSetting = mPackages.valueAt(i); + if (packageSetting.sharedUser == null) { List<RuntimePermissionsState.PermissionState> permissions = getPermissionsFromPermissionsState( - sharedUserSetting.getPermissionsState(), userId); - sharedUserPermissions.put(sharedUserName, permissions); + packageSetting.getPermissionsState(), userId); + packagePermissions.put(packageName, permissions); } + } - runtimePermissions = new RuntimePermissionsState(version, fingerprint, - packagePermissions, sharedUserPermissions); + Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = + new ArrayMap<>(); + final int sharedUsersSize = mSharedUsers.size(); + for (int i = 0; i < sharedUsersSize; i++) { + String sharedUserName = mSharedUsers.keyAt(i); + SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i); + List<RuntimePermissionsState.PermissionState> permissions = + getPermissionsFromPermissionsState( + sharedUserSetting.getPermissionsState(), userId); + sharedUserPermissions.put(sharedUserName, permissions); } + RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version, + fingerprint, packagePermissions, sharedUserPermissions); + mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId)); } @NonNull private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState( - @NonNull PermissionsState permissionsState, @UserIdInt int userId) { - List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates( - userId); - List<RuntimePermissionsState.PermissionState> permissions = - new ArrayList<>(); - int permissionStatesSize = permissionStates.size(); - for (int i = 0; i < permissionStatesSize; i++) { - PermissionState permissionState = permissionStates.get(i); - + @NonNull AppIdPermissionState permissionsState, @UserIdInt int userId) { + Collection<PermissionState> permissionStates = + permissionsState.getRuntimePermissionStates(userId); + List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>(); + for (PermissionState permissionState : permissionStates) { RuntimePermissionsState.PermissionState permission = new RuntimePermissionsState.PermissionState(permissionState.getName(), permissionState.isGranted(), permissionState.getFlags()); @@ -5480,7 +5466,7 @@ public final class Settings { userId)); if (runtimePermissions == null) { readLegacyStateForUserSyncLPr(userId); - writePermissionsForUserAsyncLPr(userId); + writeStateForUserAsyncLPr(userId); return; } @@ -5536,7 +5522,7 @@ public final class Settings { private void readPermissionsStateLpr( @NonNull List<RuntimePermissionsState.PermissionState> permissions, - @NonNull PermissionsState permissionsState, @UserIdInt int userId) { + @NonNull AppIdPermissionState permissionsState, @UserIdInt int userId) { int permissionsSize = permissions.size(); for (int i = 0; i < permissionsSize; i++) { RuntimePermissionsState.PermissionState permission = permissions.get(i); @@ -5550,14 +5536,8 @@ public final class Settings { boolean granted = permission.isGranted(); int flags = permission.getFlags(); - if (granted) { - permissionsState.grantRuntimePermission(basePermission, userId); - permissionsState.updatePermissionFlags(basePermission, userId, - PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); - } else { - permissionsState.updatePermissionFlags(basePermission, userId, - PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); - } + permissionsState.putRuntimePermissionState(new PermissionState(basePermission, + granted, flags), userId); } } @@ -5638,8 +5618,9 @@ public final class Settings { } } - private void parsePermissionsLPr(XmlPullParser parser, PermissionsState permissionsState, - int userId) throws IOException, XmlPullParserException { + private void parsePermissionsLPr(XmlPullParser parser, + AppIdPermissionState permissionsState, int userId) + throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -5666,15 +5647,8 @@ public final class Settings { final int flags = (flagsStr != null) ? Integer.parseInt(flagsStr, 16) : 0; - if (granted) { - permissionsState.grantRuntimePermission(bp, userId); - permissionsState.updatePermissionFlags(bp, userId, - PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); - } else { - permissionsState.updatePermissionFlags(bp, userId, - PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); - } - + permissionsState.putRuntimePermissionState(new PermissionState(bp, granted, + flags), userId); } break; } @@ -5690,7 +5664,9 @@ public final class Settings { public void handleMessage(Message message) { final int userId = message.what; Runnable callback = (Runnable) message.obj; - writePermissionsSync(userId); + synchronized (mLock) { + writeStateForUserSyncLPr(userId); + } if (callback != null) { callback.run(); } diff --git a/services/core/java/com/android/server/pm/permission/AppIdPermissionState.java b/services/core/java/com/android/server/pm/permission/AppIdPermissionState.java new file mode 100644 index 000000000000..aabdafdd453a --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/AppIdPermissionState.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2015 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.server.pm.permission; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +/** + * Legacy permission state that was associated with packages or shared users. + */ +//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +public final class AppIdPermissionState { + // Maps from user IDs to user states. + @NonNull + private final SparseArray<UserState> mUserStates = new SparseArray<>(); + + // Keyed by user IDs. + @NonNull + private final SparseBooleanArray mMissing = new SparseBooleanArray(); + + /** + * Copy from another permission state. + * + * @param other the other permission state. + * + * @hide + */ + public void copyFrom(@NonNull AppIdPermissionState other) { + if (other == this) { + return; + } + + mUserStates.clear(); + final int userStatesSize = other.mUserStates.size(); + for (int i = 0; i < userStatesSize; i++) { + mUserStates.put(other.mUserStates.keyAt(i), + new UserState(other.mUserStates.valueAt(i))); + } + + mMissing.clear(); + final int missingSize = other.mMissing.size(); + for (int i = 0; i < missingSize; i++) { + mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i)); + } + } + + /** + * Reset this permission state. + * + * @hide + */ + public void reset() { + mUserStates.clear(); + mMissing.clear(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + final AppIdPermissionState other = (AppIdPermissionState) object; + return Objects.equals(mUserStates, other.mUserStates) + && Objects.equals(mMissing, other.mMissing); + } + + /** + * Put a install permission state. + * + * @param permissionState the permission state + */ + public void putInstallPermissionState(@NonNull PermissionState permissionState) { + putPermissionState(permissionState, UserHandle.USER_ALL); + } + + /** + * Put a runtime permission state for a user. + * + * @param permissionState the permission state + * @param userId the user ID + */ + public void putRuntimePermissionState(@NonNull PermissionState permissionState, + @UserIdInt int userId) { + checkUserId(userId); + putPermissionState(permissionState, userId); + } + + private void putPermissionState(@NonNull PermissionState permissionState, + @UserIdInt int userId) { + UserState userState = mUserStates.get(userId); + if (userState == null) { + userState = new UserState(); + mUserStates.put(userId, userState); + } + userState.putPermissionState(permissionState); + } + + /** + * Check whether there are any permission states for the given permissions. + * + * @param permissionNames the permission names + * @return whether there are any permission states + * + * @hide + */ + public boolean hasPermissionState(@NonNull Collection<String> permissionNames) { + final int userStatesSize = mUserStates.size(); + for (int i = 0; i < userStatesSize; i++) { + final UserState userState = mUserStates.valueAt(i); + for (final String permissionName : permissionNames) { + if (userState.getPermissionState(permissionName) != null) { + return true; + } + } + } + return false; + } + + /** + * Get all the install permission states. + * + * @return the install permission states + */ + @NonNull + public Collection<PermissionState> getInstallPermissionStates() { + return getPermissionStates(UserHandle.USER_ALL); + } + + /** + * Get all the runtime permission states for a user. + * + * @param userId the user ID + * @return the runtime permission states + */ + @NonNull + public Collection<PermissionState> getRuntimePermissionStates(@UserIdInt int userId) { + checkUserId(userId); + return getPermissionStates(userId); + } + + @NonNull + private Collection<PermissionState> getPermissionStates(@UserIdInt int userId) { + final UserState userState = mUserStates.get(userId); + if (userState == null) { + return Collections.emptyList(); + } + return userState.getPermissionStates(); + } + + /** + * Check whether the permission state is missing for a user. + * <p> + * This can happen if permission state is rolled back and we'll need to generate a reasonable + * default state to keep the app usable. + * + * @param userId the user ID + * @return whether the permission state is missing + */ + public boolean isMissing(@UserIdInt int userId) { + checkUserId(userId); + return mMissing.get(userId); + } + + /** + * Set whether the permission state is missing for a user. + * <p> + * This can happen if permission state is rolled back and we'll need to generate a reasonable + * default state to keep the app usable. + * + * @param missing whether the permission state is missing + * @param userId the user ID + */ + public void setMissing(boolean missing, @UserIdInt int userId) { + checkUserId(userId); + if (missing) { + mMissing.put(userId, true); + } else { + mMissing.delete(userId); + } + } + + private static void checkUserId(@UserIdInt int userId) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid user ID " + userId); + } + } + + /** + * Legacy state for permissions for a user. + */ + private static final class UserState { + // Maps from permission names to permission states. + @NonNull + private final ArrayMap<String, PermissionState> mPermissionStates = new ArrayMap<>(); + + public UserState() {} + + public UserState(@NonNull UserState other) { + final int permissionStatesSize = other.mPermissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + mPermissionStates.put(other.mPermissionStates.keyAt(i), + new PermissionState(other.mPermissionStates.valueAt(i))); + } + } + + @Nullable + public PermissionState getPermissionState(@NonNull String permissionName) { + return mPermissionStates.get(permissionName); + } + + public void putPermissionState(@NonNull PermissionState permissionState) { + mPermissionStates.put(permissionState.getName(), permissionState); + } + + @NonNull + public Collection<PermissionState> getPermissionStates() { + return Collections.unmodifiableCollection(mPermissionStates.values()); + } + } + + /** + * Legacy state for a single permission. + */ + public static final class PermissionState { + @NonNull + private final BasePermission mPermission; + + private final boolean mGranted; + + private final int mFlags; + + /** + * Create a new instance of this class. + * + * @param permission the {@link BasePermission} for the permission + * @param granted whether the permission is granted + * @param flags the permission flags + */ + public PermissionState(@NonNull BasePermission permission, boolean granted, int flags) { + mPermission = permission; + mGranted = granted; + mFlags = flags; + } + + private PermissionState(@NonNull PermissionState other) { + mPermission = other.mPermission; + mGranted = other.mGranted; + mFlags = other.mFlags; + } + + /** + * Get the {@link BasePermission} for the permission. + * + * @return the {@link BasePermission} + */ + @NonNull + public BasePermission getPermission() { + return mPermission; + } + + /** + * Get the permission name. + * + * @return the permission name + */ + @NonNull + public String getName() { + return mPermission.getName(); + } + + /** + * Get whether the permission is granted. + * + * @return whether the permission is granted + */ + @NonNull + public boolean isGranted() { + return mGranted; + } + + /** + * Get the permission flags. + * + * @return the permission flags + */ + @NonNull + public int getFlags() { + return mFlags; + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java new file mode 100644 index 000000000000..7452b522b20a --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.permission; + +import android.annotation.AppIdInt; +import android.annotation.NonNull; + +/** + * An interface for legacy code to read permission data in order to maintain compatibility. + */ +//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +public interface LegacyPermissionDataProvider { + /** + * Get the legacy permission state of an app ID, either a package or a shared user. + * + * @param appId the app ID + * @return the legacy permission state + */ + @NonNull + public abstract AppIdPermissionState getAppIdPermissionState(@AppIdInt int appId); + + /** + * Get the GIDs computed from the permission state of a UID, either a package or a shared user. + * + * @param uid the UID + * @return the GIDs for the UID + */ + @NonNull + public abstract int[] getGidsForUid(int uid); +} diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index aa327ba02356..544f1225916e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -58,6 +58,7 @@ import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPE import static java.util.concurrent.TimeUnit.SECONDS; import android.Manifest; +import android.annotation.AppIdInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -137,7 +138,6 @@ import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.Watchdog; -import com.android.server.am.ActivityManagerService; import com.android.server.pm.ApexManager; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageSetting; @@ -902,16 +902,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private int checkPermissionImpl(String permName, String pkgName, int userId) { - try { - enforceCrossUserOrProfilePermission(Binder.getCallingUid(), userId, - false, false, "checkPermissionImpl"); - } catch (Exception e) { - Slog.e(TAG, "Invalid cross user access", e); - EventLog.writeEvent(0x534e4554, "153996875", "checkPermissionImpl", pkgName); - - throw e; - } - final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName); if (pkg == null) { return PackageManager.PERMISSION_DENIED; @@ -989,16 +979,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private int checkUidPermissionImpl(String permName, int uid) { - try { - enforceCrossUserOrProfilePermission(Binder.getCallingUid(), UserHandle.getUserId(uid), - false, false, "checkUidPermissionImpl"); - } catch (Exception e) { - Slog.e(TAG, "Invalid cross user access", e); - EventLog.writeEvent(0x534e4554, "153996875", "checkUidPermissionImpl", uid); - - throw e; - } - final AndroidPackage pkg = mPackageManagerInt.getPackage(uid); return checkUidPermissionInternal(pkg, uid, permName); } @@ -2535,20 +2515,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { return permission.computeGids(userId); } - @Nullable - private int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) { - final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName); - if (ps == null) { - return null; - } - final UidPermissionState uidState = getUidState(ps, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); - return null; - } - return uidState.computeGids(userId); - } - /** * Restore the permission state for a package. * @@ -4529,7 +4495,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } final int callingUserId = UserHandle.getUserId(callingUid); if (hasCrossUserPermission( - Binder.getCallingPid(), callingUid, callingUserId, userId, requireFullPermission, + callingUid, callingUserId, userId, requireFullPermission, requirePermissionWhenSameUser)) { return; } @@ -4556,79 +4522,53 @@ public class PermissionManagerService extends IPermissionManager.Stub { private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message) { - int callingPid = Binder.getCallingPid(); - final int callingUserId = UserHandle.getUserId(callingUid); - if (userId < 0) { throw new IllegalArgumentException("Invalid userId " + userId); } - - if (callingUserId == userId) { + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, + /*requirePermissionWhenSameUser= */ false)) { return; } - - // Prevent endless loop between when checking permission while checking a permission - if (callingPid == ActivityManagerService.MY_PID) { + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); + if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight( + mContext, + android.Manifest.permission.INTERACT_ACROSS_PROFILES, + PermissionChecker.PID_UNKNOWN, + callingUid, + mPackageManagerInt.getPackage(callingUid).getPackageName()) + == PermissionChecker.PERMISSION_GRANTED) { return; } - - long token = Binder.clearCallingIdentity(); - try { - if (checkShell) { - PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, - UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); - } - if (hasCrossUserPermission(callingPid, callingUid, callingUserId, userId, - requireFullPermission, /*requirePermissionWhenSameUser= */ false)) { - return; - } - final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); - - if (isSameProfileGroup) { - AndroidPackage callingPkg = mPackageManagerInt.getPackage(callingUid); - String callingPkgName = null; - if (callingPkg != null) { - callingPkgName = callingPkg.getPackageName(); - } - - if (PermissionChecker.checkPermissionForPreflight( - mContext, - android.Manifest.permission.INTERACT_ACROSS_PROFILES, - PermissionChecker.PID_UNKNOWN, - callingUid, - callingPkgName) - == PermissionChecker.PERMISSION_GRANTED) { - return; - } - } - String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( callingUid, userId, message, requireFullPermission, isSameProfileGroup); Slog.w(TAG, errorMessage); throw new SecurityException(errorMessage); - } finally { - Binder.restoreCallingIdentity(token); - } } - private boolean hasCrossUserPermission(int callingPid, int callingUid, int callingUserId, - int userId, boolean requireFullPermission, boolean requirePermissionWhenSameUser) { + private boolean hasCrossUserPermission( + int callingUid, int callingUserId, int userId, boolean requireFullPermission, + boolean requirePermissionWhenSameUser) { if (!requirePermissionWhenSameUser && userId == callingUserId) { return true; } if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { return true; } - - if (!requireFullPermission) { - if (mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, - callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) { - return true; - } + if (requireFullPermission) { + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); } + return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + } - return mContext.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, - callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; + private boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; } private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { @@ -4813,7 +4753,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - private void removeAppState(int appId) { + private void removeAppIdState(@AppIdInt int appId) { synchronized (mLock) { final int[] userIds = mState.getUserIds(); for (final int userId : userIds) { @@ -4827,7 +4767,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int[] userIds = getAllUserIds(); mPackageManagerInt.forEachPackageSetting(ps -> { final int appId = ps.getAppId(); - final PermissionsState permissionsState = ps.getPermissionsState(); + final AppIdPermissionState appIdState = ps.getPermissionsState(); synchronized (mLock) { for (final int userId : userIds) { @@ -4836,25 +4776,22 @@ public class PermissionManagerService extends IPermissionManager.Stub { userState.setInstallPermissionsFixed(ps.name, ps.areInstallPermissionsFixed()); final UidPermissionState uidState = userState.getOrCreateUidState(appId); uidState.reset(); - uidState.setGlobalGids(permissionsState.getGlobalGids()); - uidState.setMissing(permissionsState.isMissing(userId)); + uidState.setMissing(appIdState.isMissing(userId)); readStateFromPermissionStates(uidState, - permissionsState.getInstallPermissionStates(), false); + appIdState.getInstallPermissionStates(), false); readStateFromPermissionStates(uidState, - permissionsState.getRuntimePermissionStates(userId), true); + appIdState.getRuntimePermissionStates(userId), true); } } }); } private void readStateFromPermissionStates(@NonNull UidPermissionState uidState, - @NonNull List<PermissionsState.PermissionState> permissionStates, boolean isRuntime) { - final int permissionStatesSize = permissionStates.size(); - for (int i = 0; i < permissionStatesSize; i++) { - final PermissionsState.PermissionState permissionState = permissionStates.get(i); - final BasePermission permission = permissionState.getPermission(); - uidState.putPermissionState(permission, isRuntime, permissionState.isGranted(), - permissionState.getFlags()); + @NonNull Collection<AppIdPermissionState.PermissionState> permissionStates, + boolean isRuntime) { + for (final AppIdPermissionState.PermissionState permissionState : permissionStates) { + uidState.putPermissionState(permissionState.getPermission(), isRuntime, + permissionState.isGranted(), permissionState.getFlags()); } } @@ -4862,8 +4799,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int[] userIds = mState.getUserIds(); mPackageManagerInt.forEachPackageSetting(ps -> { ps.setInstallPermissionsFixed(false); - final PermissionsState permissionsState = ps.getPermissionsState(); - permissionsState.reset(); + final AppIdPermissionState appIdState = ps.getPermissionsState(); + appIdState.reset(); final int appId = ps.getAppId(); synchronized (mLock) { @@ -4885,27 +4822,21 @@ public class PermissionManagerService extends IPermissionManager.Stub { continue; } - permissionsState.setGlobalGids(uidState.getGlobalGids()); - permissionsState.setMissing(uidState.isMissing(), userId); + appIdState.setMissing(uidState.isMissing(), userId); final List<PermissionState> permissionStates = uidState.getPermissionStates(); final int permissionStatesSize = permissionStates.size(); for (int i = 0; i < permissionStatesSize; i++) { final PermissionState permissionState = permissionStates.get(i); - final BasePermission permission = permissionState.getPermission(); - if (permissionState.isGranted()) { - if (permissionState.isRuntime()) { - permissionsState.grantRuntimePermission(permission, userId); - } else { - permissionsState.grantInstallPermission(permission); - } - } - final int flags = permissionState.getFlags(); - if (flags != 0) { - final int flagsUserId = permissionState.isRuntime() ? userId - : UserHandle.USER_ALL; - permissionsState.updatePermissionFlags(permission, flagsUserId, flags, - flags); + final AppIdPermissionState.PermissionState legacyPermissionState = + new AppIdPermissionState.PermissionState( + permissionState.getPermission(), + permissionState.isGranted(), permissionState.getFlags()); + if (permissionState.isRuntime()) { + appIdState.putRuntimePermissionState(legacyPermissionState, + userId); + } else { + appIdState.putInstallPermissionState(legacyPermissionState); } } } @@ -4913,6 +4844,48 @@ public class PermissionManagerService extends IPermissionManager.Stub { }); } + @NonNull + private AppIdPermissionState getAppIdPermissionState(@AppIdInt int appId) { + final AppIdPermissionState appIdState = new AppIdPermissionState(); + final int[] userIds = mState.getUserIds(); + for (final int userId : userIds) { + final UidPermissionState uidState = getUidState(appId, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + + userId); + continue; + } + + final List<PermissionState> permissionStates = uidState.getPermissionStates(); + final int permissionStatesSize = permissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + final PermissionState permissionState = permissionStates.get(i); + + final AppIdPermissionState.PermissionState legacyPermissionState = + new AppIdPermissionState.PermissionState(permissionState.getPermission(), + permissionState.isGranted(), permissionState.getFlags()); + if (permissionState.isRuntime()) { + appIdState.putRuntimePermissionState(legacyPermissionState, userId); + } else if (userId == UserHandle.USER_SYSTEM) { + appIdState.putInstallPermissionState(legacyPermissionState); + } + } + } + return appIdState; + } + + @NonNull + private int[] getGidsForUid(int uid) { + final int appId = UserHandle.getAppId(uid); + final int userId = UserHandle.getUserId(uid); + final UidPermissionState uidState = getUidState(appId, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + userId); + return EMPTY_INT_ARRAY; + } + return uidState.computeGids(userId); + } + private class PermissionManagerServiceInternalImpl extends PermissionManagerServiceInternal { @Override public void systemReady() { @@ -4957,8 +4930,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.onUserRemoved(userId); } @Override - public void removePermissionsStateTEMP(int appId) { - PermissionManagerService.this.removeAppState(appId); + public void removeAppIdStateTEMP(@AppIdInt int appId) { + PermissionManagerService.this.removeAppIdState(appId); } @Override @UserIdInt @@ -4978,11 +4951,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { return PermissionManagerService.this.getPermissionGids(permissionName, userId); } - @Nullable - @Override - public int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) { - return PermissionManagerService.this.getPackageGids(packageName, userId); - } @Override public void grantRequestedRuntimePermissions(AndroidPackage pkg, int[] userIds, String[] grantedPermissions, int callingUid) { @@ -5310,6 +5278,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } } + + @NonNull + public AppIdPermissionState getAppIdPermissionState(@AppIdInt int appId) { + return PermissionManagerService.this.getAppIdPermissionState(appId); + } + + @NonNull + public int[] getGidsForUid(int uid) { + return PermissionManagerService.this.getGidsForUid(uid); + } } private static final class OnPermissionChangeListeners extends Handler { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 6d9bd2a1b30d..5ea3458fcbfa 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -37,8 +37,8 @@ import java.util.function.Consumer; * * TODO: Should be merged into PermissionManagerInternal, but currently uses internal classes. */ -public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal { - +public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal + implements LegacyPermissionDataProvider { /** * Provider for package names. */ @@ -288,13 +288,13 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager public abstract void onUserRemoved(@UserIdInt int userId); /** - * Remove the {@code PermissionsState} associated with an app ID, called the same time as the + * Remove the permission state associated with an app ID, called the same time as the * removal of a {@code PackageSetitng}. * * TODO(zhanghai): This is a temporary method before we figure out a way to get notified of app * ID removal via API. */ - public abstract void removePermissionsStateTEMP(int appId); + public abstract void removeAppIdStateTEMP(@AppIdInt int appId); /** * Update the shared user setting when a package with a shared user id is removed. The gids @@ -324,12 +324,6 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager @Nullable public abstract int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId); - /** - * Get the GIDs computed from the permission state of a package. - */ - @Nullable - public abstract int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId); - /** Retrieve the packages that have requested the given app op permission */ public abstract @Nullable String[] getAppOpPermissionPackages( @NonNull String permName, int callingUid); diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java deleted file mode 100644 index 4fb2d5fc200e..000000000000 --- a/services/core/java/com/android/server/pm/permission/PermissionsState.java +++ /dev/null @@ -1,970 +0,0 @@ -/* - * Copyright (C) 2015 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.server.pm.permission; - -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.content.pm.PackageManager; -import android.os.UserHandle; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.SparseArray; -import android.util.SparseBooleanArray; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.ArrayUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -/** - * This class encapsulates the permissions for a package or a shared user. - * <p> - * There are two types of permissions: install (granted at installation) - * and runtime (granted at runtime). Install permissions are granted to - * all device users while runtime permissions are granted explicitly to - * specific users. - * </p> - * <p> - * The permissions are kept on a per device user basis. For example, an - * application may have some runtime permissions granted under the device - * owner but not granted under the secondary user. - * <p> - * This class is also responsible for keeping track of the Linux gids per - * user for a package or a shared user. The gids are computed as a set of - * the gids for all granted permissions' gids on a per user basis. - * </p> - */ -public final class PermissionsState { - - /** The permission operation failed. */ - public static final int PERMISSION_OPERATION_FAILURE = -1; - - /** The permission operation succeeded and no gids changed. */ - public static final int PERMISSION_OPERATION_SUCCESS = 0; - - /** The permission operation succeeded and gids changed. */ - public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1; - - private static final int[] NO_GIDS = {}; - - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private ArrayMap<String, PermissionData> mPermissions; - - private int[] mGlobalGids = NO_GIDS; - - @Nullable - private SparseBooleanArray mMissing; - - private SparseBooleanArray mPermissionReviewRequired; - - public PermissionsState() { - /* do nothing */ - } - - public PermissionsState(PermissionsState prototype) { - copyFrom(prototype); - } - - public int[] getGlobalGids() { - return mGlobalGids; - } - - /** - * Sets the global gids, applicable to all users. - * - * @param globalGids The global gids. - */ - public void setGlobalGids(int[] globalGids) { - if (!ArrayUtils.isEmpty(globalGids)) { - mGlobalGids = Arrays.copyOf(globalGids, globalGids.length); - } - } - - private static void invalidateCache() { - PackageManager.invalidatePackageInfoCache(); - } - - /** - * Initialized this instance from another one. - * - * @param other The other instance. - */ - public void copyFrom(PermissionsState other) { - if (other == this) { - return; - } - - synchronized (mLock) { - if (mPermissions != null) { - if (other.mPermissions == null) { - mPermissions = null; - } else { - mPermissions.clear(); - } - } - if (other.mPermissions != null) { - if (mPermissions == null) { - mPermissions = new ArrayMap<>(); - } - final int permissionCount = other.mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String name = other.mPermissions.keyAt(i); - PermissionData permissionData = other.mPermissions.valueAt(i); - mPermissions.put(name, new PermissionData(permissionData)); - } - } - } - - mGlobalGids = NO_GIDS; - if (other.mGlobalGids != NO_GIDS) { - mGlobalGids = Arrays.copyOf(other.mGlobalGids, - other.mGlobalGids.length); - } - - if (mMissing != null) { - if (other.mMissing == null) { - mMissing = null; - } else { - mMissing.clear(); - } - } - if (other.mMissing != null) { - if (mMissing == null) { - mMissing = new SparseBooleanArray(); - } - final int missingSize = other.mMissing.size(); - for (int i = 0; i < missingSize; i++) { - mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i)); - } - } - - if (mPermissionReviewRequired != null) { - if (other.mPermissionReviewRequired == null) { - mPermissionReviewRequired = null; - } else { - mPermissionReviewRequired.clear(); - } - } - if (other.mPermissionReviewRequired != null) { - if (mPermissionReviewRequired == null) { - mPermissionReviewRequired = new SparseBooleanArray(); - } - final int userCount = other.mPermissionReviewRequired.size(); - for (int i = 0; i < userCount; i++) { - final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i); - mPermissionReviewRequired.put(other.mPermissionReviewRequired.keyAt(i), - reviewRequired); - } - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final PermissionsState other = (PermissionsState) obj; - - synchronized (mLock) { - if (mPermissions == null) { - if (other.mPermissions != null) { - return false; - } - } else if (!mPermissions.equals(other.mPermissions)) { - return false; - } - } - - if (!Objects.equals(mMissing, other.mMissing)) { - return false; - } - - if (mPermissionReviewRequired == null) { - if (other.mPermissionReviewRequired != null) { - return false; - } - } else if (!mPermissionReviewRequired.equals(other.mPermissionReviewRequired)) { - return false; - } - return Arrays.equals(mGlobalGids, other.mGlobalGids); - } - - /** - * Check whether the permissions state is missing for a user. This can happen if permission - * state is rolled back and we'll need to generate a reasonable default state to keep the app - * usable. - */ - public boolean isMissing(@UserIdInt int userId) { - return mMissing != null && mMissing.get(userId); - } - - /** - * Set whether the permissions state is missing for a user. This can happen if permission state - * is rolled back and we'll need to generate a reasonable default state to keep the app usable. - */ - public void setMissing(boolean missing, @UserIdInt int userId) { - if (missing) { - if (mMissing == null) { - mMissing = new SparseBooleanArray(); - } - mMissing.put(userId, true); - } else { - if (mMissing != null) { - mMissing.delete(userId); - if (mMissing.size() == 0) { - mMissing = null; - } - } - } - } - - public boolean isPermissionReviewRequired(int userId) { - return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId); - } - - /** - * Grant an install permission. - * - * @param permission The permission to grant. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. - */ - public int grantInstallPermission(BasePermission permission) { - return grantPermission(permission, UserHandle.USER_ALL); - } - - /** - * Revoke an install permission. - * - * @param permission The permission to revoke. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. - */ - public int revokeInstallPermission(BasePermission permission) { - return revokePermission(permission, UserHandle.USER_ALL); - } - - /** - * Grant a runtime permission for a given device user. - * - * @param permission The permission to grant. - * @param userId The device user id. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. - */ - public int grantRuntimePermission(BasePermission permission, int userId) { - enforceValidUserId(userId); - if (userId == UserHandle.USER_ALL) { - return PERMISSION_OPERATION_FAILURE; - } - return grantPermission(permission, userId); - } - - /** - * Revoke a runtime permission for a given device user. - * - * @param permission The permission to revoke. - * @param userId The device user id. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. - */ - public int revokeRuntimePermission(BasePermission permission, int userId) { - enforceValidUserId(userId); - if (userId == UserHandle.USER_ALL) { - return PERMISSION_OPERATION_FAILURE; - } - return revokePermission(permission, userId); - } - - /** - * Gets whether this state has a given runtime permission for a - * given device user id. - * - * @param name The permission name. - * @param userId The device user id. - * @return Whether this state has the permission. - */ - public boolean hasRuntimePermission(String name, int userId) { - enforceValidUserId(userId); - return !hasInstallPermission(name) && hasPermission(name, userId); - } - - /** - * Gets whether this state has a given install permission. - * - * @param name The permission name. - * @return Whether this state has the permission. - */ - public boolean hasInstallPermission(String name) { - return hasPermission(name, UserHandle.USER_ALL); - } - - /** - * Gets whether the state has a given permission for the specified - * user, regardless if this is an install or a runtime permission. - * - * @param name The permission name. - * @param userId The device user id. - * @return Whether the user has the permission. - */ - public boolean hasPermission(String name, int userId) { - enforceValidUserId(userId); - - synchronized (mLock) { - if (mPermissions == null) { - return false; - } - PermissionData permissionData = mPermissions.get(name); - - return permissionData != null && permissionData.isGranted(userId); - } - - } - - /** - * Returns whether the state has any known request for the given permission name, - * whether or not it has been granted. - */ - public boolean hasRequestedPermission(ArraySet<String> names) { - synchronized (mLock) { - if (mPermissions == null) { - return false; - } - for (int i=names.size()-1; i>=0; i--) { - if (mPermissions.get(names.valueAt(i)) != null) { - return true; - } - } - } - - return false; - } - - /** - * Returns whether the state has any known request for the given permission name, - * whether or not it has been granted. - */ - public boolean hasRequestedPermission(String name) { - return mPermissions != null && (mPermissions.get(name) != null); - } - /** - * Gets all permissions for a given device user id regardless if they - * are install time or runtime permissions. - * - * @param userId The device user id. - * @return The permissions or an empty set. - */ - public Set<String> getPermissions(int userId) { - enforceValidUserId(userId); - - synchronized (mLock) { - if (mPermissions == null) { - return Collections.emptySet(); - } - - Set<String> permissions = new ArraySet<>(mPermissions.size()); - - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String permission = mPermissions.keyAt(i); - - if (hasInstallPermission(permission)) { - permissions.add(permission); - continue; - } - - if (userId != UserHandle.USER_ALL) { - if (hasRuntimePermission(permission, userId)) { - permissions.add(permission); - } - } - } - - return permissions; - } - } - - /** - * Gets the state for an install permission or null if no such. - * - * @param name The permission name. - * @return The permission state. - */ - public PermissionState getInstallPermissionState(String name) { - return getPermissionState(name, UserHandle.USER_ALL); - } - - /** - * Gets the state for a runtime permission or null if no such. - * - * @param name The permission name. - * @param userId The device user id. - * @return The permission state. - */ - public PermissionState getRuntimePermissionState(String name, int userId) { - enforceValidUserId(userId); - return getPermissionState(name, userId); - } - - /** - * Gets all install permission states. - * - * @return The permission states or an empty set. - */ - public List<PermissionState> getInstallPermissionStates() { - return getPermissionStatesInternal(UserHandle.USER_ALL); - } - - /** - * Gets all runtime permission states. - * - * @return The permission states or an empty set. - */ - public List<PermissionState> getRuntimePermissionStates(int userId) { - enforceValidUserId(userId); - return getPermissionStatesInternal(userId); - } - - /** - * Gets the flags for a permission regardless if it is install or - * runtime permission. - * - * @param name The permission name. - * @return The permission state or null if no such. - */ - public int getPermissionFlags(String name, int userId) { - PermissionState installPermState = getInstallPermissionState(name); - if (installPermState != null) { - return installPermState.getFlags(); - } - PermissionState runtimePermState = getRuntimePermissionState(name, userId); - if (runtimePermState != null) { - return runtimePermState.getFlags(); - } - return 0; - } - - /** - * Update the flags associated with a given permission. - * @param permission The permission whose flags to update. - * @param userId The user for which to update. - * @param flagMask Mask for which flags to change. - * @param flagValues New values for the mask flags. - * @return Whether the permission flags changed. - */ - public boolean updatePermissionFlags(BasePermission permission, int userId, - int flagMask, int flagValues) { - enforceValidUserId(userId); - - final boolean mayChangeFlags = flagValues != 0 || flagMask != 0; - - synchronized (mLock) { - if (mPermissions == null) { - if (!mayChangeFlags) { - return false; - } - ensurePermissionData(permission); - } - } - - PermissionData permissionData = null; - synchronized (mLock) { - permissionData = mPermissions.get(permission.getName()); - } - - if (permissionData == null) { - if (!mayChangeFlags) { - return false; - } - permissionData = ensurePermissionData(permission); - } - - final int oldFlags = permissionData.getFlags(userId); - - final boolean updated = permissionData.updateFlags(userId, flagMask, flagValues); - if (updated) { - final int newFlags = permissionData.getFlags(userId); - if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0 - && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - if (mPermissionReviewRequired == null) { - mPermissionReviewRequired = new SparseBooleanArray(); - } - mPermissionReviewRequired.put(userId, true); - } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0 - && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { - if (mPermissionReviewRequired != null && !hasPermissionRequiringReview(userId)) { - mPermissionReviewRequired.delete(userId); - if (mPermissionReviewRequired.size() <= 0) { - mPermissionReviewRequired = null; - } - } - } - } - return updated; - } - - private boolean hasPermissionRequiringReview(int userId) { - synchronized (mLock) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - final PermissionData permission = mPermissions.valueAt(i); - if ((permission.getFlags(userId) - & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - return true; - } - } - } - - return false; - } - - public boolean updatePermissionFlagsForAllPermissions( - int userId, int flagMask, int flagValues) { - enforceValidUserId(userId); - - synchronized (mLock) { - if (mPermissions == null) { - return false; - } - boolean changed = false; - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - PermissionData permissionData = mPermissions.valueAt(i); - changed |= permissionData.updateFlags(userId, flagMask, flagValues); - } - - return changed; - } - } - - /** - * Compute the Linux gids for a given device user from the permissions - * granted to this user. Note that these are computed to avoid additional - * state as they are rarely accessed. - * - * @param userId The device user id. - * @return The gids for the device user. - */ - public int[] computeGids(int userId) { - enforceValidUserId(userId); - - int[] gids = mGlobalGids; - - synchronized (mLock) { - if (mPermissions != null) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String permission = mPermissions.keyAt(i); - if (!hasPermission(permission, userId)) { - continue; - } - PermissionData permissionData = mPermissions.valueAt(i); - final int[] permGids = permissionData.computeGids(userId); - if (permGids != NO_GIDS) { - gids = appendInts(gids, permGids); - } - } - } - } - - return gids; - } - - /** - * Compute the Linux gids for all device users from the permissions - * granted to these users. - * - * @return The gids for all device users. - */ - public int[] computeGids(int[] userIds) { - int[] gids = mGlobalGids; - - for (int userId : userIds) { - final int[] userGids = computeGids(userId); - gids = appendInts(gids, userGids); - } - - return gids; - } - - /** - * Resets the internal state of this object. - */ - public void reset() { - mGlobalGids = NO_GIDS; - - synchronized (mLock) { - mPermissions = null; - invalidateCache(); - } - - mMissing = null; - mPermissionReviewRequired = null; - } - - private PermissionState getPermissionState(String name, int userId) { - synchronized (mLock) { - if (mPermissions == null) { - return null; - } - PermissionData permissionData = mPermissions.get(name); - if (permissionData == null) { - return null; - } - - return permissionData.getPermissionState(userId); - } - } - - private List<PermissionState> getPermissionStatesInternal(int userId) { - enforceValidUserId(userId); - - synchronized (mLock) { - if (mPermissions == null) { - return Collections.emptyList(); - } - - List<PermissionState> permissionStates = new ArrayList<>(); - - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - PermissionData permissionData = mPermissions.valueAt(i); - - PermissionState permissionState = permissionData.getPermissionState(userId); - if (permissionState != null) { - permissionStates.add(permissionState); - } - } - - return permissionStates; - } - } - - private int grantPermission(BasePermission permission, int userId) { - if (hasPermission(permission.getName(), userId)) { - return PERMISSION_OPERATION_SUCCESS; - } - - final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); - final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; - - PermissionData permissionData = ensurePermissionData(permission); - - if (!permissionData.grant(userId)) { - return PERMISSION_OPERATION_FAILURE; - } - - if (hasGids) { - final int[] newGids = computeGids(userId); - if (oldGids.length != newGids.length) { - return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; - } - } - - return PERMISSION_OPERATION_SUCCESS; - } - - private int revokePermission(BasePermission permission, int userId) { - final String permName = permission.getName(); - if (!hasPermission(permName, userId)) { - return PERMISSION_OPERATION_SUCCESS; - } - - final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); - final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; - - PermissionData permissionData = null; - synchronized (mLock) { - permissionData = mPermissions.get(permName); - } - - if (!permissionData.revoke(userId)) { - return PERMISSION_OPERATION_FAILURE; - } - - if (permissionData.isDefault()) { - ensureNoPermissionData(permName); - } - - if (hasGids) { - final int[] newGids = computeGids(userId); - if (oldGids.length != newGids.length) { - return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; - } - } - - return PERMISSION_OPERATION_SUCCESS; - } - - // TODO: fix this to use arraycopy and append all ints in one go - private static int[] appendInts(int[] current, int[] added) { - if (current != null && added != null) { - for (int guid : added) { - current = ArrayUtils.appendInt(current, guid); - } - } - return current; - } - - private static void enforceValidUserId(int userId) { - if (userId != UserHandle.USER_ALL && userId < 0) { - throw new IllegalArgumentException("Invalid userId:" + userId); - } - } - - private PermissionData ensurePermissionData(BasePermission permission) { - final String permName = permission.getName(); - - synchronized (mLock) { - if (mPermissions == null) { - mPermissions = new ArrayMap<>(); - } - PermissionData permissionData = mPermissions.get(permName); - if (permissionData == null) { - permissionData = new PermissionData(permission); - mPermissions.put(permName, permissionData); - } - return permissionData; - } - - } - - private void ensureNoPermissionData(String name) { - synchronized (mLock) { - if (mPermissions == null) { - return; - } - mPermissions.remove(name); - if (mPermissions.isEmpty()) { - mPermissions = null; - } - } - - } - - private static final class PermissionData { - - private final Object mLock = new Object(); - - private final BasePermission mPerm; - @GuardedBy("mLock") - private SparseArray<PermissionState> mUserStates = new SparseArray<>(); - - public PermissionData(BasePermission perm) { - mPerm = perm; - } - - public PermissionData(PermissionData other) { - this(other.mPerm); - - synchronized (mLock) { - final int otherStateCount = other.mUserStates.size(); - for (int i = 0; i < otherStateCount; i++) { - final int otherUserId = other.mUserStates.keyAt(i); - PermissionState otherState = other.mUserStates.valueAt(i); - mUserStates.put(otherUserId, new PermissionState(otherState)); - } - } - } - - public int[] computeGids(int userId) { - return mPerm.computeGids(userId); - } - - public boolean isGranted(int userId) { - synchronized (mLock) { - if (isInstallPermission()) { - userId = UserHandle.USER_ALL; - } - - PermissionState userState = mUserStates.get(userId); - if (userState == null) { - return false; - } - - return userState.mGranted; - } - } - - public boolean grant(int userId) { - synchronized (mLock) { - if (!isCompatibleUserId(userId)) { - return false; - } - - if (isGranted(userId)) { - return false; - } - - PermissionState userState = mUserStates.get(userId); - if (userState == null) { - userState = new PermissionState(mPerm); - mUserStates.put(userId, userState); - } - - userState.mGranted = true; - - invalidateCache(); - return true; - } - } - - public boolean revoke(int userId) { - synchronized (mLock) { - if (!isCompatibleUserId(userId)) { - return false; - } - - if (!isGranted(userId)) { - return false; - } - - PermissionState userState = mUserStates.get(userId); - userState.mGranted = false; - - if (userState.isDefault()) { - mUserStates.remove(userId); - } - - invalidateCache(); - return true; - } - } - - public PermissionState getPermissionState(int userId) { - synchronized (mLock) { - return mUserStates.get(userId); - } - } - - public int getFlags(int userId) { - synchronized (mLock) { - PermissionState userState = mUserStates.get(userId); - if (userState != null) { - return userState.mFlags; - } - return 0; - } - } - - public boolean isDefault() { - synchronized (mLock) { - return mUserStates.size() <= 0; - } - } - - public static boolean isInstallPermissionKey(int userId) { - return userId == UserHandle.USER_ALL; - } - - public boolean updateFlags(int userId, int flagMask, int flagValues) { - synchronized (mLock) { - if (isInstallPermission()) { - userId = UserHandle.USER_ALL; - } - - if (!isCompatibleUserId(userId)) { - return false; - } - - final int newFlags = flagValues & flagMask; - - // Okay to do before the modification because we hold the lock. - invalidateCache(); - - PermissionState userState = mUserStates.get(userId); - if (userState != null) { - final int oldFlags = userState.mFlags; - userState.mFlags = (userState.mFlags & ~flagMask) | newFlags; - if (userState.isDefault()) { - mUserStates.remove(userId); - } - return userState.mFlags != oldFlags; - } else if (newFlags != 0) { - userState = new PermissionState(mPerm); - userState.mFlags = newFlags; - mUserStates.put(userId, userState); - return true; - } - - return false; - } - } - - private boolean isCompatibleUserId(int userId) { - return isDefault() || !(isInstallPermission() ^ isInstallPermissionKey(userId)); - } - - private boolean isInstallPermission() { - return mUserStates.size() == 1 - && mUserStates.get(UserHandle.USER_ALL) != null; - } - } - - public static final class PermissionState { - private final BasePermission mPermission; - private boolean mGranted; - private int mFlags; - - public PermissionState(BasePermission permission) { - mPermission = permission; - } - - public PermissionState(PermissionState other) { - mPermission = other.mPermission; - mGranted = other.mGranted; - mFlags = other.mFlags; - } - - public boolean isDefault() { - return !mGranted && mFlags == 0; - } - - public BasePermission getPermission() { - return mPermission; - } - - public String getName() { - return mPermission.getName(); - } - - public boolean isGranted() { - return mGranted; - } - - public int getFlags() { - return mFlags; - } - } -} diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING index 65dc320eadc2..c0d71ac26853 100644 --- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING @@ -18,24 +18,21 @@ ] }, { - "name": "CtsPermission2TestCases", + "name": "CtsAppSecurityHostTestCases", "options": [ { - "include-filter": "android.permission2.cts.RestrictedPermissionsTest" - }, - { - "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest" + "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission" } ] }, { - "name": "CtsPermissionHostTestCases" - }, - { - "name": "CtsAppSecurityHostTestCases", + "name": "CtsPermission2TestCases", "options": [ { - "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission" + "include-filter": "android.permission2.cts.RestrictedPermissionsTest" + }, + { + "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest" } ] }, diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index 1d31285ed9c7..de8823c4b7f3 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -40,7 +40,10 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; import android.os.HidlMemoryUtil; +import android.os.ParcelFileDescriptor; +import java.io.FileDescriptor; +import java.io.IOException; import java.util.regex.Matcher; /** @@ -196,8 +199,18 @@ class ConversionUtil { hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type); hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid); hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid); - hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(aidlModel.data, - aidlModel.dataSize); + + // Extract a dup of the underlying FileDescriptor out of aidlModel.data without changing + // the original. + FileDescriptor fd = new FileDescriptor(); + try { + ParcelFileDescriptor dup = aidlModel.data.dup(); + fd.setInt$(dup.detachFd()); + hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(fd, aidlModel.dataSize); + } catch (IOException e) { + throw new RuntimeException(e); + } + return hidlModel; } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 6305fda4924c..958a7a8f07f8 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -16,9 +16,13 @@ package com.android.server.wm; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; + import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.protolog.common.ProtoLog; + import java.util.Set; /** @@ -63,25 +67,38 @@ class BLASTSyncEngine { private void tryFinish() { if (mRemainingTransactions == 0 && mReady) { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Finished. Reporting %d " + + "containers to %s", BLASTSyncEngine.this.hashCode(), mSyncId, + mWindowContainersReady.size(), mListener); mListener.onTransactionReady(mSyncId, mWindowContainersReady); mPendingSyncs.remove(mSyncId); } } - public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) { + public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) { mRemainingTransactions--; + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Child ready, now ready=%b" + + " and waiting on %d transactions", BLASTSyncEngine.this.hashCode(), mSyncId, + mReady, mRemainingTransactions); mWindowContainersReady.addAll(windowContainersReady); tryFinish(); } void setReady() { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Set ready", + BLASTSyncEngine.this.hashCode(), mSyncId); mReady = true; tryFinish(); } boolean addToSync(WindowContainer wc) { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Trying to add %s", + BLASTSyncEngine.this.hashCode(), mSyncId, wc); if (wc.prepareForSync(this, mSyncId)) { mRemainingTransactions++; + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Added %s. now waiting " + + "on %d transactions", BLASTSyncEngine.this.hashCode(), mSyncId, wc, + mRemainingTransactions); return true; } return false; @@ -105,6 +122,7 @@ class BLASTSyncEngine { final int id = mNextSyncId++; final SyncState s = new SyncState(listener, id); mPendingSyncs.put(id, s); + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Start for %s", hashCode(), id, listener); return id; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index ae8f7a556ffd..2b93080a8dad 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -32,6 +32,7 @@ import static android.view.SurfaceControl.Transaction; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION; import static com.android.server.wm.IdentifierProto.HASH_CODE; @@ -2886,6 +2887,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // If we are invisible, no need to sync, likewise if we are already engaged in a sync, // we can't support overlapping syncs on a single container yet. if (!isVisible() || mWaitingListener != null) { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "- NOT adding to sync: visible=%b " + + "hasListener=%b", isVisible(), mWaitingListener != null); return false; } mUsingBLASTSyncTransaction = true; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 67d230aae0fb..59f209aca6ba 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -41,7 +41,6 @@ import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.Task.ActivityState.STARTED; import static com.android.server.wm.Task.ActivityState.STOPPING; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -465,8 +464,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio * Allows background activity starts using token {@code entity}. Optionally, you can provide * {@code originatingToken} if you have one such originating token, this is useful for tracing * back the grant in the case of the notification token. + * + * If {@code entity} is already added, this method will update its {@code originatingToken}. */ - public void addAllowBackgroundActivityStartsToken(Binder entity, + public void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity, @Nullable IBinder originatingToken) { synchronized (mAtm.mGlobalLock) { mBackgroundActivityStartTokens.put(entity, originatingToken); @@ -475,7 +476,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio /** * Removes token {@code entity} that allowed background activity starts added via {@link - * #addAllowBackgroundActivityStartsToken(Binder, IBinder)}. + * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}. */ public void removeAllowBackgroundActivityStartsToken(Binder entity) { synchronized (mAtm.mGlobalLock) { @@ -485,7 +486,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio /** * Returns true if background activity starts are allowed by any token added via {@link - * #addAllowBackgroundActivityStartsToken(Binder, IBinder)}. + * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}. */ public boolean areBackgroundActivityStartsAllowedByToken() { synchronized (mAtm.mGlobalLock) { @@ -1575,10 +1576,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio pw.print(prefix); pw.print("mVrThreadTid="); pw.println(mVrThreadTid); } if (mBackgroundActivityStartTokens.size() > 0) { - pw.print(prefix); pw.println("Background activity start tokens:"); + pw.print(prefix); + pw.println("Background activity start tokens (token: originating token):"); for (int i = 0; i < mBackgroundActivityStartTokens.size(); i++) { pw.print(prefix); pw.print(" - "); - pw.println(mBackgroundActivityStartTokens.keyAt(i)); + pw.print(mBackgroundActivityStartTokens.keyAt(i)); + pw.print(": "); + pw.println(mBackgroundActivityStartTokens.valueAt(i)); + } } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 5e814005a5e2..029c158814b3 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -441,10 +441,6 @@ class WindowStateAnimator { return mSurfaceController; } - if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) { - windowType = SurfaceControl.WINDOW_TYPE_DONT_SCREENSHOT; - } - w.setHasSurface(false); if (DEBUG_ANIM) { @@ -462,6 +458,10 @@ class WindowStateAnimator { flags |= SurfaceControl.SECURE; } + if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) { + flags |= SurfaceControl.SKIP_SCREENSHOT; + } + calculateSurfaceBounds(w, attrs, mTmpSize); final int width = mTmpSize.width(); final int height = mTmpSize.height(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java index 5193fa85d238..b6b4d8a04cb6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java @@ -58,4 +58,12 @@ class CallerIdentity { @Nullable public ComponentName getComponentName() { return mComponentName; } + + public boolean hasAdminComponent() { + return mComponentName != null; + } + + public boolean hasPackage() { + return mPackageName != null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 183a1495b075..8f1fb127ec17 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1567,6 +1567,54 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * Creates a new {@link CallerIdentity} object to represent the caller's identity. If no + * component name is provided, look up the component name and fill it in for the caller. + */ + private CallerIdentity getCallerIdentityOptionalAdmin(@Nullable ComponentName adminComponent) { + if (adminComponent == null) { + ActiveAdmin admin = getActiveAdminOfCaller(); + if (admin != null) { + return getCallerIdentity(admin.info.getComponent()); + } + throw new SecurityException("Caller is not an active admin"); + } else { + return getCallerIdentity(adminComponent); + } + } + + /** + * Creates a new {@link CallerIdentity} object to represent the caller's identity. If no + * package name is provided, look up the package name and fill it in for the caller. + */ + private CallerIdentity getCallerIdentityOptionalPackage(@Nullable String callerPackage) { + if (callerPackage == null) { + ActiveAdmin admin = getActiveAdminOfCaller(); + if (admin != null) { + return getCallerIdentity(admin.info.getPackageName()); + } + throw new SecurityException("Caller is not an active admin"); + } else { + return getCallerIdentity(callerPackage); + } + } + + /** + * Retrieves the active admin of the caller. This method should not be called directly and + * should only be called by {@link #getCallerIdentityOptionalAdmin} or + * {@link #getCallerIdentityOptionalPackage}. + */ + private ActiveAdmin getActiveAdminOfCaller() { + final int callerUid = mInjector.binderGetCallingUid(); + final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid)); + for (ActiveAdmin admin : policy.mAdminList) { + if (admin.getUid() == callerUid) { + return admin; + } + } + return null; + } + + /** * Checks if the device is in COMP mode, and if so migrates it to managed profile on a * corporate owned device. */ @@ -3071,7 +3119,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = getCallerIdentity(adminReceiver); + final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -3630,6 +3678,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) { + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(admin); Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); List<String> changedProviders = null; @@ -3663,6 +3713,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) { + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(admin); Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); List<String> changedProviders = null; @@ -3696,6 +3748,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<String> getCrossProfileWidgetProviders(ComponentName admin) { + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(admin); Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); @@ -4833,8 +4887,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { byte[] cert, byte[] chain, String alias, boolean requestAccess, boolean isUserSelectable) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))); final long id = mInjector.binderClearCallingIdentity(); try { @@ -4872,8 +4927,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean removeKeyPair(ComponentName who, String callerPackage, String alias) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))); final long id = Binder.clearCallingIdentity(); try { @@ -4908,8 +4964,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkStringNotEmpty(packageName, "Package to grant to cannot be empty"); final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))); final int granteeUid; try { @@ -5052,8 +5109,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { enforceCallerCanRequestDeviceIdAttestation(caller); enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags); } else { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))); } // As the caller will be granted access to the key, ensure no UID was specified, as @@ -5147,8 +5205,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias, byte[] cert, byte[] chain, boolean isUserSelectable) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))); final long id = mInjector.binderClearCallingIdentity(); try (final KeyChainConnection keyChainConnection = @@ -5613,6 +5672,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean setAlwaysOnVpnPackage(ComponentName who, String vpnPackage, boolean lockdown, List<String> lockdownWhitelist) throws SecurityException { + Objects.requireNonNull(who, "ComponentName is null"); + enforceProfileOrDeviceOwner(who); final CallerIdentity caller = getCallerIdentity(who); @@ -5752,16 +5813,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final CallerIdentity caller = getCallerIdentity(); boolean calledByProfileOwnerOnOrgOwnedDevice = - isProfileOwnerOfOrganizationOwnedDevice(caller); + isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()); if (calledOnParentInstance) { Preconditions.checkCallAuthorization(calledByProfileOwnerOnOrgOwnedDevice, "Wiping the entire device can only be done by a profile owner on " + "organization-owned device."); } if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { - Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || calledByProfileOwnerOnOrgOwnedDevice, - "Only device owners or proflie owners of organization-owned device can set " + Preconditions.checkCallAuthorization(isCallerDeviceOwner(caller.getUid()) + || calledByProfileOwnerOnOrgOwnedDevice, + "Only device owners or profile owners of organization-owned device can set " + "WIPE_RESET_PROTECTION_DATA"); } @@ -5960,9 +6021,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = comp != null - ? getCallerIdentity(comp) - : getCallerIdentity(); + final CallerIdentity caller = getCallerIdentityOptionalAdmin(comp); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN)); @@ -6399,9 +6458,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = who != null - ? getCallerIdentity(who) - : getCallerIdentity(); + final CallerIdentity caller = getCallerIdentityOptionalAdmin(who); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -6435,9 +6492,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = callerPackage != null - ? getCallerIdentity(callerPackage) - : getCallerIdentity(); + final CallerIdentity caller = getCallerIdentityOptionalPackage(callerPackage); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); // It's not critical here, but let's make sure the package name is correct, in case @@ -6546,9 +6601,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } if (parent) { - Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity(who))); + isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId())); } synchronized (getLockObject()) { @@ -6934,9 +6988,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } if (parent) { - Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity(who))); + isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId())); } synchronized (getLockObject()) { @@ -7070,8 +7123,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(packageList, "packageList is null"); final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + || (caller.hasPackage() + && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES))); synchronized (getLockObject()) { // Get the device owner @@ -7097,8 +7151,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + || (caller.hasPackage() + && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES))); // TODO In split system user mode, allow apps on user 0 to query the list synchronized (getLockObject()) { @@ -8517,8 +8572,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setApplicationRestrictions(ComponentName who, String callerPackage, String packageName, Bundle settings) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))); mInjector.binderWithCleanCallingIdentity(() -> { mUserManager.setApplicationRestrictions(packageName, settings, @@ -8558,9 +8614,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(agent, "agent null"); Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); - final CallerIdentity caller = admin != null - ? getCallerIdentity(admin) - : getCallerIdentity(); + final CallerIdentity caller = getCallerIdentityOptionalAdmin(admin); Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); synchronized (getLockObject()) { @@ -9442,8 +9496,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public Bundle getApplicationRestrictions(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))); return mInjector.binderWithCleanCallingIdentity(() -> { Bundle bundle = mUserManager.getApplicationRestrictions(packageName, @@ -9458,8 +9513,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public String[] setPackagesSuspended(ComponentName who, String callerPackage, String[] packageNames, boolean suspended) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); String[] result = null; synchronized (getLockObject()) { @@ -9489,8 +9545,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); @@ -9650,8 +9707,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName, boolean hidden, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId(); boolean result; @@ -9682,8 +9740,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean isApplicationHidden(ComponentName who, String callerPackage, String packageName, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId(); synchronized (getLockObject()) { @@ -9716,8 +9775,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void enableSystemApp(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP))); synchronized (getLockObject()) { final boolean isDemo = isCurrentUserDemo(); @@ -9759,8 +9819,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP))); int numberOfAppsInstalled = 0; synchronized (getLockObject()) { @@ -9827,8 +9888,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean installExistingPackage(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() + && isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE))); boolean result; synchronized (getLockObject()) { @@ -9941,8 +10004,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setUninstallBlocked(ComponentName who, String callerPackage, String packageName, boolean uninstallBlocked) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL))); final int userId = caller.getUserId(); synchronized (getLockObject()) { @@ -10489,6 +10553,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { + Preconditions.checkNotNull(who, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(isDeviceOwner(caller)); @@ -11596,8 +11662,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) { final CallerIdentity caller = getCallerIdentity(admin, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); synchronized (getLockObject()) { DevicePolicyData userPolicy = getUserData(caller.getUserId()); @@ -11630,8 +11697,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(callback); final CallerIdentity caller = getCallerIdentity(admin, callerPackage); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); synchronized (getLockObject()) { long ident = mInjector.binderClearCallingIdentity(); @@ -11694,9 +11762,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public int getPermissionGrantState(ComponentName admin, String callerPackage, String packageName, String permission) throws RemoteException { final CallerIdentity caller = getCallerIdentity(admin, callerPackage); - Preconditions.checkCallAuthorization( - isSystemUid(caller) || isDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)); + Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent() + && (isProfileOwner(caller) || isDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); synchronized (getLockObject()) { return mInjector.binderWithCleanCallingIdentity(() -> { @@ -13149,8 +13217,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); synchronized (getLockObject()) { if (enabled == isNetworkLoggingEnabledInternalLocked()) { @@ -13267,9 +13335,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING) - || hasCallingOrSelfPermission(permission.MANAGE_USERS)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)) + || hasCallingOrSelfPermission(permission.MANAGE_USERS)); synchronized (getLockObject()) { return isNetworkLoggingEnabledInternalLocked(); @@ -13295,8 +13363,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)); + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked()); synchronized (getLockObject()) { @@ -14494,6 +14562,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<String> getUserControlDisabledPackages(ComponentName who) { + Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(isDeviceOwner(caller)); diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 44a07a17824f..e8bf468f032e 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -308,6 +308,7 @@ IncrementalService::~IncrementalService() { } mJobCondition.notify_all(); mJobProcessor.join(); + mLooper->wake(); mCmdLooperThread.join(); mTimedQueue->stop(); mProgressUpdateJobQueue->stop(); @@ -1386,7 +1387,7 @@ bool IncrementalService::mountExistingImage(std::string_view root) { } void IncrementalService::runCmdLooper() { - constexpr auto kTimeoutMsecs = 1000; + constexpr auto kTimeoutMsecs = -1; while (mRunning.load(std::memory_order_relaxed)) { mLooper->pollAll(kTimeoutMsecs); } diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java new file mode 100644 index 000000000000..488e5cdf33b9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.BatteryStatsImpl; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +@RunWith(AndroidJUnit4.class) +public final class BatteryStatsServiceTest { + + private BatteryStatsService mBatteryStatsService; + private HandlerThread mBgThread; + + @Before + public void setUp() { + final Context context = InstrumentationRegistry.getContext(); + mBgThread = new HandlerThread("bg thread"); + mBgThread.start(); + mBatteryStatsService = new BatteryStatsService(context, + context.getCacheDir(), new Handler(mBgThread.getLooper())); + } + + @After + public void tearDown() { + mBatteryStatsService.shutdown(); + mBgThread.quitSafely(); + } + + @Test + public void testAwaitCompletion() throws Exception { + final CountDownLatch readyLatch = new CountDownLatch(2); + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch testLatch = new CountDownLatch(1); + final AtomicBoolean quiting = new AtomicBoolean(false); + final AtomicBoolean finished = new AtomicBoolean(false); + final int uid = Process.myUid(); + final Thread noteThread = new Thread(() -> { + final int maxIterations = 1000; + final int eventCode = 12345; + final String eventName = "placeholder"; + final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + + readyLatch.countDown(); + try { + startLatch.await(); + } catch (InterruptedException e) { + } + + for (int i = 0; i < maxIterations && !quiting.get(); i++) { + synchronized (stats) { + mBatteryStatsService.noteEvent(eventCode, eventName, uid); + } + } + finished.set(true); + }); + final Thread waitThread = new Thread(() -> { + readyLatch.countDown(); + try { + startLatch.await(); + } catch (InterruptedException e) { + } + + do { + mBatteryStatsService.takeUidSnapshot(uid); + } while (!finished.get() && !quiting.get()); + + if (!quiting.get()) { + // do one more to ensure we've cleared the queue + mBatteryStatsService.takeUidSnapshot(uid); + } + + testLatch.countDown(); + }); + noteThread.start(); + waitThread.start(); + readyLatch.await(); + startLatch.countDown(); + + try { + assertTrue("Timed out in waiting for the completion of battery event handling", + testLatch.await(10 * 1000, TimeUnit.MILLISECONDS)); + } finally { + quiting.set(true); + noteThread.interrupt(); + noteThread.join(1000); + waitThread.interrupt(); + waitThread.join(1000); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index dd4c0814a441..c60d5fb95846 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -95,14 +95,14 @@ public class HdmiCecControllerTest { /** Tests for {@link HdmiCecController#allocateLogicalAddress} */ @Test - public void testAllocatLogicalAddress_TvDevicePreferredNotOcupied() { + public void testAllocateLogicalAddress_TvDevicePreferredNotOccupied() { mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_TV, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_TV, mLogicalAddress); } @Test - public void testAllocatLogicalAddress_TvDeviceNonPreferredNotOcupied() { + public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() { mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); @@ -110,7 +110,7 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_TvDeviceNonPreferredFirstOcupied() { + public void testAllocateLogicalAddress_TvDeviceNonPreferredFirstOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); @@ -118,7 +118,7 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_TvDeviceNonPreferredAllOcupied() { + public void testAllocateLogicalAddress_TvDeviceNonPreferredAllOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS); mNativeWrapper.setPollAddressResponse(ADDR_SPECIFIC_USE, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback); @@ -127,7 +127,7 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_AudioSystemNonPreferredNotOcupied() { + public void testAllocateLogicalAddress_AudioSystemNonPreferredNotOccupied() { mHdmiCecController.allocateLogicalAddress( DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); @@ -135,7 +135,7 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_AudioSystemNonPreferredAllOcupied() { + public void testAllocateLogicalAddress_AudioSystemNonPreferredAllOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress( DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback); @@ -144,14 +144,14 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_PlaybackPreferredNotOccupied() { + public void testAllocateLogicalAddress_PlaybackPreferredNotOccupied() { mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_PLAYBACK_1, mLogicalAddress); } @Test - public void testAllocatLogicalAddress_PlaybackPreferredOcuppied() { + public void testAllocateLogicalAddress_PlaybackPreferredOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback); mTestLooper.dispatchAll(); @@ -159,14 +159,14 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_PlaybackNoPreferredNotOcuppied() { + public void testAllocateLogicalAddress_PlaybackNoPreferredNotOccupied() { mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_PLAYBACK_1, mLogicalAddress); } @Test - public void testAllocatLogicalAddress_PlaybackNoPreferredFirstOcuppied() { + public void testAllocateLogicalAddress_PlaybackNoPreferredFirstOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); @@ -174,7 +174,7 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_PlaybackNonPreferredFirstTwoOcuppied() { + public void testAllocateLogicalAddress_PlaybackNonPreferredFirstTwoOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_2, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback); @@ -183,7 +183,7 @@ public class HdmiCecControllerTest { } @Test - public void testAllocatLogicalAddress_PlaybackNonPreferredAllOcupied() { + public void testAllocateLogicalAddress_PlaybackNonPreferredAllOccupied() { mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_2, SendMessageResult.SUCCESS); mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_3, SendMessageResult.SUCCESS); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index dac05424e01f..bc747832e253 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -62,6 +62,7 @@ import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.PermissionSettings; import com.google.common.truth.Truth; @@ -94,6 +95,8 @@ public class PackageManagerSettingsTests { PermissionSettings mPermissionSettings; @Mock RuntimePermissionsPersistence mRuntimePermissionsPersistence; + @Mock + LegacyPermissionDataProvider mPermissionDataProvider; @Before public void initializeMocks() { @@ -115,7 +118,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); assertThat(settings.readLPw(createFakeUsers()), is(true)); verifyKeySetMetaData(settings); } @@ -129,7 +132,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); assertThat(settings.readLPw(createFakeUsers()), is(true)); // write out, read back in and verify the same @@ -145,7 +148,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); assertThat(settings.readLPw(createFakeUsers()), is(true)); assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue())); assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue())); @@ -167,13 +170,13 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); assertThat(settings.readLPw(createFakeUsers()), is(true)); settings.writeLPr(); // Create Settings again to make it read from the new files settings = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); assertThat(settings.readLPw(createFakeUsers()), is(true)); PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2); @@ -196,7 +199,8 @@ public class PackageManagerSettingsTests { writePackageRestrictions_noSuspendingPackageXml(0); final Object lock = new Object(); final Context context = InstrumentationRegistry.getTargetContext(); - final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, lock); + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null, + lock); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2)); settingsUnderTest.readPackageRestrictionsLPr(0); @@ -219,7 +223,8 @@ public class PackageManagerSettingsTests { writePackageRestrictions_noSuspendParamsMapXml(0); final Object lock = new Object(); final Context context = InstrumentationRegistry.getTargetContext(); - final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, lock); + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null, + lock); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); settingsUnderTest.readPackageRestrictionsLPr(0); @@ -246,7 +251,7 @@ public class PackageManagerSettingsTests { @Test public void testReadWritePackageRestrictions_suspendInfo() { final Context context = InstrumentationRegistry.getTargetContext(); - final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null, new Object()); final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2); @@ -344,7 +349,7 @@ public class PackageManagerSettingsTests { @Test public void testReadWritePackageRestrictions_distractionFlags() { final Context context = InstrumentationRegistry.getTargetContext(); - final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null, new Object()); final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2); @@ -389,7 +394,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getTargetContext(); final Object lock = new Object(); final Settings settingsUnderTest = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); ps1.appId = Process.FIRST_APPLICATION_UID; ps1.pkg = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()) @@ -465,7 +470,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); assertThat(settings.readLPw(createFakeUsers()), is(true)); // Enable/Disable a package @@ -638,7 +643,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); final Settings testSettings01 = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); final SharedUserSetting testUserSetting01 = createSharedUserSetting( testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/); final PackageSetting testPkgSetting01 = @@ -748,7 +753,7 @@ public class PackageManagerSettingsTests { final Context context = InstrumentationRegistry.getContext(); final Object lock = new Object(); final Settings testSettings01 = new Settings(context.getFilesDir(), mPermissionSettings, - mRuntimePermissionsPersistence, lock); + mRuntimePermissionsPersistence, mPermissionDataProvider, lock); final SharedUserSetting testUserSetting01 = createSharedUserSetting( testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/); final PackageSetting testPkgSetting01 = Settings.createNewSetting( diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index ebcf10dd019f..509eb2563376 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -59,6 +59,7 @@ import android.os.HidlMemoryUtil; import android.os.HwParcel; import android.os.IHwBinder; import android.os.IHwInterface; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SharedMemory; import android.system.ErrnoException; @@ -126,7 +127,7 @@ public class SoundTriggerMiddlewareImplTest { model.uuid = "12345678-2345-3456-4567-abcdef987654"; model.vendorUuid = "87654321-5432-6543-7654-456789fedcba"; byte[] data = new byte[]{91, 92, 93, 94, 95}; - model.data = byteArrayToFileDescriptor(data); + model.data = new ParcelFileDescriptor(byteArrayToFileDescriptor(data)); model.dataSize = data.length; return model; } 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 9e7226e7cacf..1a4b119a6c99 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -69,6 +69,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; @@ -102,10 +103,12 @@ import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentUris; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -282,6 +285,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationHistoryManager mHistoryManager; @Mock StatsManager mStatsManager; + BroadcastReceiver mPackageIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -480,6 +484,28 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mShortcutHelper.setLauncherApps(mLauncherApps); mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal); + // Capture PackageIntentReceiver + ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentCaptor<IntentFilter> intentFilterCaptor = + ArgumentCaptor.forClass(IntentFilter.class); + + verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(), + any(), intentFilterCaptor.capture(), any(), any()); + List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues(); + List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues(); + + for (int i = 0; i < intentFilters.size(); i++) { + final IntentFilter filter = intentFilters.get(i); + if (filter.hasAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED) + && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) + && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { + mPackageIntentReceiver = broadcastReceivers.get(i); + break; + } + } + assertNotNull("package intent receiver should exist", mPackageIntentReceiver); + // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); ShortcutInfo info = mock(ShortcutInfo.class); @@ -526,7 +552,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void tearDown() throws Exception { if (mFile != null) mFile.delete(); clearDeviceConfig(); - mService.unregisterDeviceConfigChange(); + + try { + mService.onDestroy(); + } catch (IllegalStateException e) { + // can throw if a broadcast receiver was never registered + } + InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); } @@ -2533,10 +2565,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testHasCompanionDevice_noService() { - mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger, + NotificationManagerService noManService = + new TestableNotificationManagerService(mContext, mNotificationRecordLogger, mNotificationInstanceIdSequence); - assertFalse(mService.hasCompanionDevice(mListener)); + assertFalse(noManService.hasCompanionDevice(mListener)); } @Test @@ -4347,13 +4380,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(notif2); // on broadcast, hide the 2 notifications - mService.simulatePackageSuspendBroadcast(true, PKG); + simulatePackageSuspendBroadcast(true, PKG, notif1.getUid()); ArgumentCaptor<List> captorHide = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); assertEquals(2, captorHide.getValue().size()); // on broadcast, unhide the 2 notifications - mService.simulatePackageSuspendBroadcast(false, PKG); + simulatePackageSuspendBroadcast(false, PKG, notif1.getUid()); ArgumentCaptor<List> captorUnhide = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); assertEquals(2, captorUnhide.getValue().size()); @@ -4370,7 +4403,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(notif2); // on broadcast, nothing is hidden since no notifications are of package "test_package" - mService.simulatePackageSuspendBroadcast(true, "test_package"); + simulatePackageSuspendBroadcast(true, "test_package", notif1.getUid()); + ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); + verify(mListeners, times(1)).notifyHiddenLocked(captor.capture()); + assertEquals(0, captor.getValue().size()); + } + + @Test + public void testNotificationFromDifferentUserHidden() { + // post 2 notification from this package + final NotificationRecord notif1 = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + final NotificationRecord notif2 = generateNotificationRecord( + mTestNotificationChannel, 2, null, false); + mService.addNotification(notif1); + mService.addNotification(notif2); + + // on broadcast, nothing is hidden since no notifications are of user 10 with package PKG + simulatePackageSuspendBroadcast(true, PKG, 10); ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyHiddenLocked(captor.capture()); assertEquals(0, captor.getValue().size()); @@ -4387,16 +4437,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(pkgB); // on broadcast, hide one of the packages - mService.simulatePackageDistractionBroadcast( - PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"}); + simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"}, + new int[] {1000}); ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture()); assertEquals(1, captorHide.getValue().size()); assertEquals("a", captorHide.getValue().get(0).getSbn().getPackageName()); // on broadcast, unhide the package - mService.simulatePackageDistractionBroadcast( - PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"}); + simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"}, + new int[] {1000}); ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture()); assertEquals(1, captorUnhide.getValue().size()); @@ -4414,8 +4466,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(pkgB); // on broadcast, hide one of the packages - mService.simulatePackageDistractionBroadcast( - PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"}); + simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"}, + new int[] {1000, 1001}); ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class); // should be called only once. @@ -4425,8 +4478,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals("b", captorHide.getValue().get(1).getSbn().getPackageName()); // on broadcast, unhide the package - mService.simulatePackageDistractionBroadcast( - PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"}); + simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"}, + new int[] {1000, 1001}); ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class); // should be called only once. @@ -4444,8 +4498,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(notif1); // on broadcast, nothing is hidden since no notifications are of package "test_package" - mService.simulatePackageDistractionBroadcast( - PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"}); + simulatePackageDistractionBroadcast( + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"}, + new int[]{notif1.getUid()}); ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); verify(mListeners, times(1)).notifyHiddenLocked(captor.capture()); assertEquals(0, captor.getValue().size()); @@ -7011,4 +7066,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertTrue(mService.isVisibleToListener(sbn, info)); } + private void simulatePackageSuspendBroadcast(boolean suspend, String pkg, + int uid) { + // mimics receive broadcast that package is (un)suspended + // but does not actually (un)suspend the package + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, + new String[]{pkg}); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid}); + + final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED + : Intent.ACTION_PACKAGES_UNSUSPENDED; + final Intent intent = new Intent(action); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + + private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) { + // mimics receive broadcast that package is (un)distracting + // but does not actually register that info with packagemanager + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids); + + final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } } 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 076047b35604..245ddb957d21 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -670,7 +670,7 @@ public class ActivityStarterTests extends WindowTestsBase { doReturn(callerIsRecents).when(recentTasks).isCallerRecents(callingUid); // caller is temp allowed if (callerIsTempAllowed) { - callerApp.addAllowBackgroundActivityStartsToken(new Binder(), null); + callerApp.addOrUpdateAllowBackgroundActivityStartsToken(new Binder(), null); } // caller is instrumenting with background activity starts privileges callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges, diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 7c150f9c8db9..0af6266d1642 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -115,13 +115,13 @@ static bool validateFile(const char* filename) { } case FILETYPE_INPUTDEVICECONFIGURATION: { - PropertyMap* map; - status_t status = PropertyMap::load(String8(filename), &map); - if (status) { - error("Error %d parsing input device configuration file.\n\n", status); + android::base::Result<std::unique_ptr<PropertyMap>> propertyMap = + PropertyMap::load(String8(filename)); + if (!propertyMap.ok()) { + error("Error %d parsing input device configuration file.\n\n", + propertyMap.error().code()); return false; } - delete map; break; } diff --git a/wifi/api/current.txt b/wifi/api/current.txt index 3f5c673eeb81..1c297e7058dd 100644 --- a/wifi/api/current.txt +++ b/wifi/api/current.txt @@ -798,9 +798,15 @@ package android.net.wifi.hotspot2.pps { method public int describeContents(); method public String getFqdn(); method public String getFriendlyName(); + method @Nullable public long[] getMatchAllOis(); + method @Nullable public long[] getMatchAnyOis(); + method @Nullable public String[] getOtherHomePartners(); method public long[] getRoamingConsortiumOis(); method public void setFqdn(String); method public void setFriendlyName(String); + method public void setMatchAllOis(@Nullable long[]); + method public void setMatchAnyOis(@Nullable long[]); + method public void setOtherHomePartners(@Nullable String[]); method public void setRoamingConsortiumOis(long[]); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR; diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java index 8f34579f6a5d..35a8ff6095e0 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java @@ -16,6 +16,7 @@ package android.net.wifi.hotspot2.pps; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -139,16 +140,26 @@ public final class HomeSp implements Parcelable { * (MO) tree for more detail. */ private long[] mMatchAllOis = null; + /** - * @hide + * Set a list of HomeOIs such that all OIs in the list must match an OI in the Roaming + * Consortium advertised by a hotspot operator. The list set by this API will have precedence + * over {@link #setMatchAnyOis(long[])}, meaning the list set in {@link #setMatchAnyOis(long[])} + * will only be used for matching if the list set by this API is null or empty. + * + * @param matchAllOis An array of longs containing the HomeOIs */ - public void setMatchAllOis(long[] matchAllOis) { + public void setMatchAllOis(@Nullable long[] matchAllOis) { mMatchAllOis = matchAllOis; } + /** - * @hide + * Get the list of HomeOIs such that all OIs in the list must match an OI in the Roaming + * Consortium advertised by a hotspot operator. + * + * @return An array of longs containing the HomeOIs */ - public long[] getMatchAllOis() { + public @Nullable long[] getMatchAllOis() { return mMatchAllOis; } @@ -159,23 +170,34 @@ public final class HomeSp implements Parcelable { * of that Hotspot provider (e.g. successful authentication with such Hotspot * is possible). * - * {@link #mMatchAllOIs} will have precedence over this one, meaning this list will - * only be used for matching if {@link #mMatchAllOIs} is null or empty. + * The list set by {@link #setMatchAllOis(long[])} will have precedence over this one, meaning + * this list will only be used for matching if the list set by {@link #setMatchAllOis(long[])} + * is null or empty. * * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object * (MO) tree for more detail. */ private long[] mMatchAnyOis = null; + /** - * @hide + * Set a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium + * advertised by a hotspot operator. The list set by {@link #setMatchAllOis(long[])} + * will have precedence over this API, meaning this list will only be used for matching if the + * list set by {@link #setMatchAllOis(long[])} is null or empty. + * + * @param matchAnyOis An array of longs containing the HomeOIs */ - public void setMatchAnyOis(long[] matchAnyOis) { + public void setMatchAnyOis(@Nullable long[] matchAnyOis) { mMatchAnyOis = matchAnyOis; } + /** - * @hide + * Get a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium + * advertised by a hotspot operator. + * + * @return An array of longs containing the HomeOIs */ - public long[] getMatchAnyOis() { + public @Nullable long[] getMatchAnyOis() { return mMatchAnyOis; } @@ -186,16 +208,25 @@ public final class HomeSp implements Parcelable { * operator merges between the providers. */ private String[] mOtherHomePartners = null; + /** - * @hide + * Set the list of FQDN (Fully Qualified Domain Name) of other Home partner providers. + * + * @param otherHomePartners Array of Strings containing the FQDNs of other Home partner + * providers */ - public void setOtherHomePartners(String[] otherHomePartners) { + public void setOtherHomePartners(@Nullable String[] otherHomePartners) { mOtherHomePartners = otherHomePartners; } + /** - * @hide + * Get the list of FQDN (Fully Qualified Domain Name) of other Home partner providers set in + * the profile. + * + * @return Array of Strings containing the FQDNs of other Home partner providers set in the + * profile */ - public String[] getOtherHomePartners() { + public @Nullable String[] getOtherHomePartners() { return mOtherHomePartners; } |