diff options
298 files changed, 10608 insertions, 5641 deletions
diff --git a/Android.bp b/Android.bp index d5e04f9f4e2d..3d56254417e1 100644 --- a/Android.bp +++ b/Android.bp @@ -373,7 +373,6 @@ java_library { "core/java/com/android/internal/appwidget/IAppWidgetHost.aidl", "core/java/com/android/internal/backup/IBackupTransport.aidl", "core/java/com/android/internal/backup/IObbBackupService.aidl", - "core/java/com/android/internal/car/ICarServiceHelper.aidl", "core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl", "core/java/com/android/internal/net/INetworkWatchlistManager.aidl", "core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl", diff --git a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java new file mode 100644 index 000000000000..f8fd51d7b0b6 --- /dev/null +++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java @@ -0,0 +1,134 @@ +/* + * 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 android.app; + +import android.content.Context; +import android.content.Intent; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.perftests.utils.StubActivity; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +// Due to b/71353150, you might get "java.lang.AssertionError: Binder ProxyMap has too many +// entries", but it's flaky. Adding "Runtime.getRuntime().gc()" between each iteration solves +// the problem, but it doesn't seem like it's currently needed. + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PendingIntentPerfTest { + + private Context mContext; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private Intent mIntent; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mIntent = StubActivity.createLaunchIntent(mContext); + } + + /** + * Benchmark time to create a PendingIntent. + */ + @Test + public void create() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + 0); + + state.pauseTiming(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to create a PendingIntent with FLAG_CANCEL_CURRENT, already having an active + * PendingIntent. + */ + @Test + public void createWithCancelFlag() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + PendingIntent.FLAG_CANCEL_CURRENT); + + state.pauseTiming(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to create a PendingIntent with FLAG_UPDATE_CURRENT, already having an active + * PendingIntent. + */ + @Test + public void createWithUpdateFlag() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + state.pauseTiming(); + previousPendingIntent.cancel(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to cancel a PendingIntent. + */ + @Test + public void cancel() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + pendingIntent.cancel(); + } + } +} + diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java index 6012f4b12b3d..8f03f7eea584 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java @@ -17,6 +17,14 @@ package android.perftests.utils; import android.app.Activity; +import android.content.Context; +import android.content.Intent; public class StubActivity extends Activity { -}
\ No newline at end of file + public static Intent createLaunchIntent(Context context) { + final Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClass(context, StubActivity.class); + return intent; + } +} diff --git a/api/current.txt b/api/current.txt index 1ffba87773ac..c03798f08fc9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6493,7 +6493,6 @@ package android.app.admin { method public boolean isNetworkLoggingEnabled(android.content.ComponentName); method public boolean isOverrideApnEnabled(android.content.ComponentName); method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; - method public boolean isPrintingEnabled(); method public boolean isProfileOwnerApp(java.lang.String); method public boolean isProvisioningAllowed(java.lang.String); method public boolean isResetPasswordTokenActive(android.content.ComponentName); @@ -6566,7 +6565,6 @@ package android.app.admin { method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>); - method public void setPrintingEnabled(android.content.ComponentName, boolean); method public void setProfileEnabled(android.content.ComponentName); method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); @@ -14605,7 +14603,7 @@ package android.graphics.drawable { method public final android.graphics.Rect copyBounds(); method public static android.graphics.drawable.Drawable createFromPath(java.lang.String); method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String); - method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options); + method public static deprecated android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options); method public static android.graphics.drawable.Drawable createFromStream(java.io.InputStream, java.lang.String); method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -16296,7 +16294,7 @@ package android.hardware.camera2 { public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult { method public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults(); - method public <T> T getPhysicalCameraKey(android.hardware.camera2.CaptureResult.Key<T>, java.lang.String); + method public java.util.Map<java.lang.String, android.hardware.camera2.CaptureResult> getPhysicalCameraResults(); } } @@ -33140,6 +33138,7 @@ package android.os { field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset"; field public static final java.lang.String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam"; field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls"; + field public static final java.lang.String DISALLOW_PRINTING = "no_printing"; field public static final java.lang.String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile"; field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user"; field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot"; @@ -48147,8 +48146,6 @@ package android.view { method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect); method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect); - method protected void onDebugDraw(android.graphics.Canvas); - method protected void onDebugDrawMargins(android.graphics.Canvas, android.graphics.Paint); method public boolean onInterceptHoverEvent(android.view.MotionEvent); method public boolean onInterceptTouchEvent(android.view.MotionEvent); method protected abstract void onLayout(boolean, int, int, int, int); @@ -48222,7 +48219,6 @@ package android.view { ctor public ViewGroup.LayoutParams(android.content.Context, android.util.AttributeSet); ctor public ViewGroup.LayoutParams(int, int); ctor public ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams); - method public void onDebugDraw(android.view.View, android.graphics.Canvas, android.graphics.Paint); method public void resolveLayoutDirection(int); method protected void setBaseAttributes(android.content.res.TypedArray, int, int); field public static final deprecated int FILL_PARENT = -1; // 0xffffffff diff --git a/api/system-current.txt b/api/system-current.txt index 70148d97e717..2d3b65a19b26 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2516,6 +2516,7 @@ package android.media { method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method public deprecated int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; method public int requestAudioFocus(android.media.AudioFocusRequest, android.media.audiopolicy.AudioPolicy); + method public void setFocusRequestResult(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy); method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy); field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1 field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4 diff --git a/api/test-current.txt b/api/test-current.txt index c30c0566b50d..4cfe4010397e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1078,6 +1078,12 @@ package android.widget { field public static final int MODE_SPINNER = 1; // 0x1 } + public final class Magnifier { + method public android.graphics.Bitmap getContent(); + method public static android.graphics.PointF getMagnifierDefaultSize(); + method public android.graphics.Rect getWindowPositionOnScreen(); + } + public class NumberPicker extends android.widget.LinearLayout { method public java.lang.CharSequence getDisplayedValueForCurrentSelection(); } diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 1d5ab59f9ba8..654036ec6ab7 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -46,6 +46,7 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) { uid_t callingUid = IPCThreadState::self()->getCallingUid(); + pid_t callingPid = IPCThreadState::self()->getCallingPid(); if (callingUid == AID_ROOT || callingUid == AID_SHELL) { // root doesn't have permission.DUMP if don't do this! return Status::ok(); @@ -54,13 +55,13 @@ checkIncidentPermissions(const IncidentReportArgs& args) // checking calling permission. if (!checkCallingPermission(DUMP_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", - IPCThreadState::self()->getCallingPid(), callingUid); + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.DUMP"); } if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", - IPCThreadState::self()->getCallingPid(), callingUid); + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.USAGE_STATS"); } @@ -68,13 +69,17 @@ checkIncidentPermissions(const IncidentReportArgs& args) // checking calling request uid permission. switch (args.dest()) { case DEST_LOCAL: - if (callingUid != AID_SHELL || callingUid != AID_ROOT) { + if (callingUid != AID_SHELL && callingUid != AID_ROOT) { + ALOGW("Calling pid %d and uid %d does not have permission to get local data.", + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission to get local data."); } case DEST_EXPLICIT: - if (callingUid != AID_SHELL || callingUid != AID_ROOT || - callingUid != AID_STATSD || callingUid != AID_SYSTEM) { + if (callingUid != AID_SHELL && callingUid != AID_ROOT && + callingUid != AID_STATSD && callingUid != AID_SYSTEM) { + ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.", + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission to get explicit data."); } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index b0019ac90708..b46c5c1b6372 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -65,6 +65,7 @@ statsd_common_src := \ src/storage/StorageManager.cpp \ src/StatsLogProcessor.cpp \ src/StatsService.cpp \ + src/subscriber/IncidentdReporter.cpp \ src/subscriber/SubscriberReporter.cpp \ src/HashableDimensionKey.cpp \ src/guardrail/MemoryLeakTrackUtil.cpp \ diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index a4066aa7bca5..e610fd757723 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -146,6 +146,12 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { return; } + long curTime = time(nullptr); + if (curTime - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) { + mStatsPullerManager.ClearPullerCacheIfNecessary(curTime); + mLastPullerCacheClearTimeSec = curTime; + } + if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) { // Map the isolated uid to host uid if necessary. mapIsolatedUidToHostUidIfNecessaryLocked(event); @@ -290,6 +296,10 @@ void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { StatsdStats::getInstance().noteConfigRemoved(key); mLastBroadcastTimes.erase(key); + + if (mMetricsManagers.empty()) { + mStatsPullerManager.ForceClearPullerCache(); + } } void StatsLogProcessor::flushIfNecessaryLocked( diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 7642aafaab43..8bbcd751252a 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -21,6 +21,7 @@ #include "logd/LogReader.h" #include "metrics/MetricsManager.h" #include "packages/UidMap.h" +#include "external/StatsPullerManager.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" @@ -75,6 +76,8 @@ private: sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid. + StatsPullerManager mStatsPullerManager; + sp<AnomalyMonitor> mAnomalyMonitor; void onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData); @@ -96,11 +99,14 @@ private: const long mTimeBaseSec; + long mLastPullerCacheClearTimeSec = 0; + FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 32da94f862c5..3efe9b18dcba 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -240,6 +240,10 @@ status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& if (!args[0].compare(String8("log-app-hook"))) { return cmd_log_app_hook(out, args); } + + if (!args[0].compare(String8("clear-puller-cache"))) { + return cmd_clear_puller_cache(out); + } } print_cmd_help(out); @@ -320,6 +324,10 @@ void StatsService::print_cmd_help(FILE* out) { fprintf(out, "\n"); fprintf(out, "usage: adb shell cmd stats print-stats\n"); fprintf(out, " Prints some basic stats.\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: adb shell cmd stats clear-puller-cache\n"); + fprintf(out, " Clear cached puller data.\n"); } status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) { @@ -602,9 +610,15 @@ status_t StatsService::cmd_dump_memory_info(FILE* out) { } status_t StatsService::cmd_clear_puller_cache(FILE* out) { - mStatsPullerManager.ClearPullerCache(); - fprintf(out, "Puller cached data removed!\n"); - return NO_ERROR; + IPCThreadState* ipc = IPCThreadState::self(); + VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); + if (checkCallingPermission(String16(kPermissionDump))) { + int cleared = mStatsPullerManager.ForceClearPullerCache(); + fprintf(out, "Puller removed %d cached data!\n", cleared); + return NO_ERROR; + } else { + return PERMISSION_DENIED; + } } Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version, diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index c84a5b4509b0..7c5e45e100a5 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -19,13 +19,11 @@ #include "AnomalyTracker.h" #include "external/Perfetto.h" -#include "guardrail/StatsdStats.h" #include "frameworks/base/libs/incident/proto/android/os/header.pb.h" +#include "guardrail/StatsdStats.h" +#include "subscriber/IncidentdReporter.h" #include "subscriber/SubscriberReporter.h" -#include <android/os/IIncidentManager.h> -#include <android/os/IncidentReportArgs.h> -#include <binder/IServiceManager.h> #include <statslog.h> #include <time.h> @@ -35,20 +33,17 @@ namespace statsd { // TODO: Get rid of bucketNumbers, and return to the original circular array method. AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) - : mAlert(alert), - mConfigKey(configKey), - mNumOfPastBuckets(mAlert.num_buckets() - 1) { + : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) { VLOG("AnomalyTracker() called"); if (mAlert.num_buckets() <= 0) { - ALOGE("Cannot create AnomalyTracker with %lld buckets", - (long long)mAlert.num_buckets()); + ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets()); return; } if (!mAlert.has_trigger_if_sum_gt()) { ALOGE("Cannot create AnomalyTracker without threshold"); return; } - resetStorage(); // initialization + resetStorage(); // initialization } AnomalyTracker::~AnomalyTracker() { @@ -171,8 +166,8 @@ bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const Metric // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this. addPastBucket(key, 0, currentBucketNum - 1); } - return mAlert.has_trigger_if_sum_gt() - && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); + return mAlert.has_trigger_if_sum_gt() && + getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); } void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) { @@ -188,7 +183,7 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDim if (!mSubscriptions.empty()) { if (mAlert.has_id()) { - ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id()); + ALOGI("An anomaly (%llu) has occurred! Informing subscribers.", mAlert.id()); informSubscribers(key); } else { ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers."); @@ -233,44 +228,26 @@ void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) { return; } - std::set<int> incidentdSections; - for (const Subscription& subscription : mSubscriptions) { switch (subscription.subscriber_information_case()) { case Subscription::SubscriberInformationCase::kIncidentdDetails: - for (int i = 0; i < subscription.incidentd_details().section_size(); i++) { - incidentdSections.insert(subscription.incidentd_details().section(i)); + if (!GenerateIncidentReport(subscription.incidentd_details(), mAlert, mConfigKey)) { + ALOGW("Failed to generate incident report."); } break; case Subscription::SubscriberInformationCase::kPerfettoDetails: - CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details()); + if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) { + ALOGW("Failed to generate prefetto traces."); + } break; case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails: - SubscriberReporter::getInstance() - .alertBroadcastSubscriber(mConfigKey, subscription, key); + SubscriberReporter::getInstance().alertBroadcastSubscriber(mConfigKey, subscription, + key); break; default: break; } } - if (!incidentdSections.empty()) { - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service != NULL) { - IncidentReportArgs incidentReport; - for (const auto section : incidentdSections) { - incidentReport.addSection(section); - } - android::os::IncidentHeaderProto header; - header.set_alert_id(mAlert.id()); - header.mutable_config_key()->set_uid(mConfigKey.GetUid()); - header.mutable_config_key()->set_id(mConfigKey.GetId()); - incidentReport.addHeader(header); - service->reportIncident(incidentReport); - } else { - ALOGW("Couldn't get the incident service."); - } - } } } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index f01a97f86cf6..3be959d14109 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -16,22 +16,24 @@ #pragma once +#include <memory> // unique_ptr + +#include <stdlib.h> + #include <gtest/gtest_prod.h> +#include <utils/RefBase.h> + #include "AnomalyMonitor.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert #include "stats_util.h" // HashableDimensionKey and DimToValMap -#include <memory> // unique_ptr -#include <stdlib.h> -#include <utils/RefBase.h> - namespace android { namespace os { namespace statsd { -using std::unordered_map; using std::shared_ptr; +using std::unordered_map; // Does NOT allow negative values. class AnomalyTracker : public virtual RefBase { @@ -60,12 +62,11 @@ public: // Detects the alert and informs the incidentd when applicable. void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum, - const MetricDimensionKey& key, - const int64_t& currentBucketValue); + const MetricDimensionKey& key, const int64_t& currentBucketValue); // Init the AnomalyMonitor which is shared across anomaly trackers. virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) { - return; // Base AnomalyTracker class has no need for the AnomalyMonitor. + return; // Base AnomalyTracker class has no need for the AnomalyMonitor. } // Helper function to return the sum value of past buckets at given dimension. @@ -92,9 +93,10 @@ public: // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker, // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor. - virtual void informAlarmsFired(const uint64_t& timestampNs, + virtual void informAlarmsFired( + const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { - return; // The base AnomalyTracker class doesn't have alarms. + return; // The base AnomalyTracker class doesn't have alarms. } protected: diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp index bbee9fa5358c..e459f7681f26 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -52,8 +52,7 @@ void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const MetricDimensionK } void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, - const uint64_t& timestampNs) { - + const uint64_t& timestampNs) { uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); if (isInRefractoryPeriod(timestampNs, dimensionKey)) { VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period"); @@ -86,15 +85,15 @@ void DurationAnomalyTracker::stopAllAlarms() { } } -void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, +void DurationAnomalyTracker::informAlarmsFired( + const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { - if (firedAlarms.empty() || mAlarms.empty()) return; // Find the intersection of firedAlarms and mAlarms. // The for loop is inefficient, since it loops over all keys, but that's okay since it is very // seldomly called. The alternative would be having AnomalyAlarms store information about the - // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is - // rarely ever called. + // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that + // is rarely ever called. unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; for (const auto& kv : mAlarms) { if (firedAlarms.count(kv.second) > 0) { diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index 052fdf576289..ba687dacf519 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -53,7 +53,8 @@ public: // and removes it from firedAlarms. // Note that this will generally be called from a different thread from the other functions; // the caller is responsible for thread safety. - void informAlarmsFired(const uint64_t& timestampNs, + void informAlarmsFired( + const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override; protected: diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index e58c53506c54..6bca16f7caa7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -1019,6 +1019,138 @@ message ForegroundServiceStateChanged { } /** + * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky + * behavior in its own uid. However, the metrics of these isolated uid's almost always should be + * attributed back to the parent (host) uid. One example is Chrome. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message IsolatedUidChanged { + // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid. + optional int32 parent_uid = 1; + + optional int32 isolated_uid = 2; + + // We expect an isolated uid to be removed before if it's used for another parent uid. + enum Event { + REMOVED = 0; + CREATED = 1; + } + optional Event event = 3; +} + +/* + * Logs the reception of an incoming network packet causing the main system to wake up for + * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd + * and processed by WakeupController.cpp. + */ +message PacketWakeupOccurred { + // The uid owning the socket into which the packet was delivered, or -1 if the packet was + // delivered nowhere. + optional int32 uid = 1; + // The interface name on which the packet was received. + optional string iface = 2; + // The ethertype value of the packet. + optional int32 ethertype = 3; + // String representation of the destination MAC address of the packet. + optional string destination_hardware_address = 4; + // String representation of the source address of the packet if this was an IP packet. + optional string source_ip = 5; + // String representation of the destination address of the packet if this was an IP packet. + optional string destination_ip = 6; + // The value of the protocol field if this was an IPv4 packet or the value of the Next Header + // field if this was an IPv6 packet. The range of possible values is the same for both IP + // families. + optional int32 ip_next_header = 7; + // The source port if this was a TCP or UDP packet. + optional int32 source_port = 8; + // The destination port if this was a TCP or UDP packet. + optional int32 destination_port = 9; +} + +/* + * Logs the memory stats for an app on startup. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message AppStartMemoryStateCaptured { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // The activity name. + optional string activity_name = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +/* + * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries + * for LMK event. + * Logged from: + * system/core/lmkd/lmkd.c + */ +message LmkStateChanged { + enum State { + UNKNOWN = 0; + START = 1; + STOP = 2; + } + optional State state = 1; +} + +/* + * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure. + * Logged from: + * system/core/lmkd/lmkd.c + */ +message LmkKillOccurred { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // oom adj score. + optional int32 oom_score = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +////////////////////////////////////////////////////////////////////// +// Pulled atoms below this line // +////////////////////////////////////////////////////////////////////// + +/** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * * Pulled from: @@ -1134,37 +1266,15 @@ message KernelWakelock { * hardware/interfaces/power/1.1/types.hal */ message SubsystemSleepState { - // Name should be in the format of XXX.YYY where XXX is subsystem name, - // YYY is corresponding voter name. - // If there are no voters, the format should just be XXX (with no dot). - // XXX and YYY should not contain a "." in it. - optional string name = 1; + // Subsystem name + optional string subsystem_name = 1; + // For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty. + // For SubsystemLowPowerStats (hal 1.1), this is the sleep state name. + optional string subname = 2; // The number of times it entered, or voted for entering the sleep state - optional uint64 count = 2; + optional uint64 count = 3; // The length of time spent in, or spent voting for, the sleep state - optional uint64 timeMs = 3; -} - -/** - * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky - * behavior in its own uid. However, the metrics of these isolated uid's almost always should be - * attributed back to the parent (host) uid. One example is Chrome. - * - * Logged from: - * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java - */ -message IsolatedUidChanged { - // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid. - optional int32 parent_uid = 1; - - optional int32 isolated_uid = 2; - - // We expect an isolated uid to be removed before if it's used for another parent uid. - enum Event { - REMOVED = 0; - CREATED = 1; - } - optional Event event = 3; + optional uint64 timeMs = 4; } /** @@ -1201,35 +1311,6 @@ message CpuTimePerUidFreq { optional uint64 time_ms = 3; } -/* - * Logs the reception of an incoming network packet causing the main system to wake up for - * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd - * and processed by WakeupController.cpp. - */ -message PacketWakeupOccurred { - // The uid owning the socket into which the packet was delivered, or -1 if the packet was - // delivered nowhere. - optional int32 uid = 1; - // The interface name on which the packet was received. - optional string iface = 2; - // The ethertype value of the packet. - optional int32 ethertype = 3; - // String representation of the destination MAC address of the packet. - optional string destination_hardware_address = 4; - // String representation of the source address of the packet if this was an IP packet. - optional string source_ip = 5; - // String representation of the destination address of the packet if this was an IP packet. - optional string destination_ip = 6; - // The value of the protocol field if this was an IPv4 packet or the value of the Next Header - // field if this was an IPv6 packet. The range of possible values is the same for both IP - // families. - optional int32 ip_next_header = 7; - // The source port if this was a TCP or UDP packet. - optional int32 source_port = 8; - // The destination port if this was a TCP or UDP packet. - optional int32 destination_port = 9; -} - /** * Pulls Wifi Controller Activity Energy Info */ @@ -1303,37 +1384,6 @@ message BluetoothActivityInfo { } /* - * Logs the memory stats for an app on startup. - * Logged from: - * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java - */ -message AppStartMemoryStateCaptured { - // The uid if available. -1 means not available. - optional int32 uid = 1; - - // The process name. - optional string process_name = 2; - - // The activity name. - optional string activity_name = 3; - - // # of page-faults - optional int64 pgfault = 4; - - // # of major page-faults - optional int64 pgmajfault = 5; - - // RSS - optional int64 rss_in_bytes = 6; - - // CACHE - optional int64 cache_in_bytes = 7; - - // SWAP - optional int64 swap_in_bytes = 8; -} - -/* * Logs the memory stats for a process. */ message ProcessMemoryState { @@ -1363,52 +1413,6 @@ message ProcessMemoryState { } /* - * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries - * for LMK event. - * Logged from: - * system/core/lmkd/lmkd.c - */ -message LmkStateChanged { - enum State { - UNKNOWN = 0; - START = 1; - STOP = 2; - } - optional State state = 1; -} - -/* - * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure. - * Logged from: - * system/core/lmkd/lmkd.c - */ -message LmkKillOccurred { - // The uid if available. -1 means not available. - optional int32 uid = 1; - - // The process name. - optional string process_name = 2; - - // oom adj score. - optional int32 oom_score = 3; - - // # of page-faults - optional int64 pgfault = 4; - - // # of major page-faults - optional int64 pgmajfault = 5; - - // RSS - optional int64 rss_in_bytes = 6; - - // CACHE - optional int64 cache_in_bytes = 7; - - // SWAP - optional int64 swap_in_bytes = 8; -} - -/* * Elapsed real time from SystemClock. */ message SystemElapsedRealtime { diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index da14434737af..41e4705ee6b6 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -59,9 +59,24 @@ bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) { return ret; } -void StatsPuller::ClearCache() { +int StatsPuller::ForceClearCache() { + return clearCache(); +} + +int StatsPuller::clearCache() { lock_guard<std::mutex> lock(mLock); + int ret = mCachedData.size(); mCachedData.clear(); + mLastPullTimeSec = 0; + return ret; +} + +int StatsPuller::ClearCacheIfNecessary(long timestampSec) { + if (timestampSec - mLastPullTimeSec > mCoolDownSec) { + return clearCache(); + } else { + return 0; + } } } // namespace statsd diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h index bc7c45f535d1..3446f9b88cdb 100644 --- a/cmds/statsd/src/external/StatsPuller.h +++ b/cmds/statsd/src/external/StatsPuller.h @@ -38,7 +38,11 @@ public: bool Pull(std::vector<std::shared_ptr<LogEvent>>* data); - void ClearCache(); + // Clear cache immediately + int ForceClearCache(); + + // Clear cache if elapsed time is more than cooldown time + int ClearCacheIfNecessary(long timestampSec); protected: // The atom tag id this puller pulls @@ -61,6 +65,8 @@ private: std::vector<std::shared_ptr<LogEvent>> mCachedData; long mLastPullTimeSec; + + int clearCache(); }; } // namespace statsd diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h index 4826d963a3ed..0dee342f0abe 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -54,8 +54,12 @@ class StatsPullerManager { mPullerManager.SetTimeBaseSec(timeBaseSec); } - void ClearPullerCache() { - mPullerManager.ClearPullerCache(); + int ForceClearPullerCache() { + return mPullerManager.ForceClearPullerCache(); + } + + int ClearPullerCacheIfNecessary(long timestampSec) { + return mPullerManager.ClearPullerCacheIfNecessary(timestampSec); } private: diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index 71b0abe25d28..19b3a8611c18 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp @@ -199,10 +199,20 @@ void StatsPullerManagerImpl::OnAlarmFired() { } } -void StatsPullerManagerImpl::ClearPullerCache() { +int StatsPullerManagerImpl::ForceClearPullerCache() { + int totalCleared = 0; for (auto puller : mPullers) { - puller.second->ClearCache(); + totalCleared += puller.second->ForceClearCache(); } + return totalCleared; +} + +int StatsPullerManagerImpl::ClearPullerCacheIfNecessary(long timestampSec) { + int totalCleared = 0; + for (auto puller : mPullers) { + totalCleared += puller.second->ClearCacheIfNecessary(timestampSec); + } + return totalCleared; } } // namespace statsd diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h index fba3ade8c1bb..3535fa3e46a0 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.h +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h @@ -49,7 +49,9 @@ public: void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;}; - void ClearPullerCache(); + int ForceClearPullerCache(); + + int ClearPullerCacheIfNecessary(long timestampSec); private: StatsPullerManagerImpl(); diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp index 6d12e257df8d..550a06497b84 100644 --- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp +++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp @@ -99,6 +99,7 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) auto statePtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE, timestamp); statePtr->write(state.name); + statePtr->write(""); statePtr->write(state.totalTransitions); statePtr->write(state.residencyInMsecSinceBoot); statePtr->init(); @@ -110,7 +111,8 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) for (auto voter : state.voters) { auto voterPtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE, timestamp); - voterPtr->write((std::string)state.name + "." + (std::string)voter.name); + voterPtr->write(state.name); + voterPtr->write(voter.name); voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot); voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); voterPtr->init(); @@ -144,7 +146,8 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) subsystem.states[j]; auto subsystemStatePtr = make_shared<LogEvent>( android::util::SUBSYSTEM_SLEEP_STATE, timestamp); - subsystemStatePtr->write((std::string)subsystem.name + "." + (std::string)state.name); + subsystemStatePtr->write(subsystem.name); + subsystemStatePtr->write(state.name); subsystemStatePtr->write(state.totalTransitions); subsystemStatePtr->write(state.residencyInMsecSinceBoot); subsystemStatePtr->init(); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 1f4bfa62c453..f254327fcc16 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -85,6 +85,9 @@ public: // Maximum size of all files that can be written to stats directory on disk. static const int kMaxFileSize = 50 * 1024 * 1024; + // How long to try to clear puller cache from last time + static const long kPullerCacheClearIntervalSec = 1; + /** * Report a new config has been received and report the static stats about the config. * diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index ae4df3ee92f0..5b5b57b61302 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -170,7 +170,6 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); mPastBuckets.clear(); - mStartTimeNs = mCurrentBucketStartTimeNs; // TODO: Clear mDimensionKeyMap once the report is dumped. } @@ -214,13 +213,11 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( } auto it = mCurrentSlicedCounter->find(eventKey); - if (it == mCurrentSlicedCounter->end()) { // ===========GuardRail============== if (hitGuardRailLocked(eventKey)) { return; } - // create a counter for the new key (*mCurrentSlicedCounter)[eventKey] = 1; } else { @@ -228,10 +225,14 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( auto& count = it->second; count++; } - for (auto& tracker : mAnomalyTrackers) { + int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second; + auto prev = mCurrentFullCounters->find(eventKey); + if (prev != mCurrentFullCounters->end()) { + countWholeBucket += prev->second; + } tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, - mCurrentSlicedCounter->find(eventKey)->second); + countWholeBucket); } VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(), @@ -241,33 +242,65 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( // When a new matched event comes in, we check if event falls into the current // bucket. If not, flush the old counter to past buckets and initialize the new bucket. void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { - if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) { + uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + if (eventTimeNs < currentBucketEndTimeNs) { return; } + flushCurrentBucketLocked(eventTimeNs); + // Setup the bucket start time and number. + uint64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, + (long long)mCurrentBucketStartTimeNs); +} + +void CountMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) { + uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); CountBucket info; info.mBucketStartNs = mCurrentBucketStartTimeNs; - info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs; + if (eventTimeNs < fullBucketEndTimeNs) { + info.mBucketEndNs = eventTimeNs; + } else { + info.mBucketEndNs = fullBucketEndTimeNs; + } info.mBucketNum = mCurrentBucketNum; for (const auto& counter : *mCurrentSlicedCounter) { info.mCount = counter.second; auto& bucketList = mPastBuckets[counter.first]; bucketList.push_back(info); - VLOG("metric %lld, dump key value: %s -> %lld", - (long long)mMetricId, counter.first.c_str(), (long long)counter.second); + VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, counter.first.c_str(), + (long long)counter.second); } - for (auto& tracker : mAnomalyTrackers) { - tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum); + // If we have finished a full bucket, then send this to anomaly tracker. + if (eventTimeNs > fullBucketEndTimeNs) { + // Accumulate partial buckets with current value and then send to anomaly tracker. + if (mCurrentFullCounters->size() > 0) { + for (const auto& keyValuePair : *mCurrentSlicedCounter) { + (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; + } + for (auto& tracker : mAnomalyTrackers) { + tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum); + } + mCurrentFullCounters = std::make_shared<DimToValMap>(); + } else { + // Skip aggregating the partial buckets since there's no previous partial bucket. + for (auto& tracker : mAnomalyTrackers) { + tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum); + } + } + } else { + // Accumulate partial bucket. + for (const auto& keyValuePair : *mCurrentSlicedCounter) { + (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; + } } - // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers). + // Only resets the counters, but doesn't setup the times nor numbers. + // (Do not clear since the old one is still referenced in mAnomalyTrackers). mCurrentSlicedCounter = std::make_shared<DimToValMap>(); - uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; - mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; - mCurrentBucketNum += numBucketsForward; - VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, - (long long)mCurrentBucketStartTimeNs); } // Rough estimate of CountMetricProducer buffer stored. This number will be diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index 8659d4773568..b06c77b27faf 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -71,14 +71,20 @@ private: void dumpStatesLocked(FILE* out, bool verbose) const override{}; // Util function to flush the old packet. - void flushIfNeededLocked(const uint64_t& newEventTime); + void flushIfNeededLocked(const uint64_t& newEventTime) override; + + void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override; // TODO: Add a lock to mPastBuckets. std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets; - // The current bucket. + // The current bucket (may be a partial bucket). std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>(); + // The sum of previous partial buckets in the current full bucket (excluding the current + // partial bucket). This is only updated while flushing the current bucket. + std::shared_ptr<DimToValMap> mCurrentFullCounters = std::make_shared<DimToValMap>(); + static const size_t kBucketSize = sizeof(CountBucket{}); bool hitGuardRailLocked(const MetricDimensionKey& newKey); @@ -87,6 +93,8 @@ private: FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); + FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade); + FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index efbdae150792..2400eba19de9 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -121,13 +121,13 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mDimensionsInCondition, mNested, - mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); + mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, + mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mDimensionsInCondition, mNested, - mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); + mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, + mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); } } @@ -252,17 +252,18 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->end(protoToken); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); mPastBuckets.clear(); - mStartTimeNs = mCurrentBucketStartTimeNs; } -void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { - if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) { +void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { + uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + + if (currentBucketEndTimeNs > eventTimeNs) { return; } VLOG("flushing..........."); for (auto it = mCurrentSlicedDurationTrackerMap.begin(); it != mCurrentSlicedDurationTrackerMap.end();) { - if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) { + if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) { VLOG("erase bucket for key %s", it->first.c_str()); it = mCurrentSlicedDurationTrackerMap.erase(it); } else { @@ -270,11 +271,23 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { } } - int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs; - mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs; + int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; } +void DurationMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) { + for (auto it = mCurrentSlicedDurationTrackerMap.begin(); + it != mCurrentSlicedDurationTrackerMap.end();) { + if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) { + VLOG("erase bucket for key %s", it->first.c_str()); + it = mCurrentSlicedDurationTrackerMap.erase(it); + } else { + ++it; + } + } +} + void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { if (mCurrentSlicedDurationTrackerMap.size() == 0) { return; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 152e570df467..a49601655e5e 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -73,6 +73,8 @@ private: // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); + void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override; + const DurationMetric_AggregationType mAggregationType; // Index of the SimpleAtomMatcher which defines the start. @@ -112,6 +114,10 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); + FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade); + FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket); + FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade); + FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket); FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); }; diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 820d5918a92a..a021e0a4d6c8 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -118,7 +118,6 @@ void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, reinterpret_cast<char*>(buffer.get()->data()), buffer.get()->size()); startNewProtoOutputStreamLocked(); - mStartTimeNs = dumpTimeNs; } void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index d6cb1891288a..4190f0048cc2 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -191,10 +191,20 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); mPastBuckets.clear(); - mStartTimeNs = mCurrentBucketStartTimeNs; // TODO: Clear mDimensionKeyMap once the report is dumped. } +void GaugeMetricProducer::pullLocked() { + vector<std::shared_ptr<LogEvent>> allData; + if (!mStatsPullerManager->Pull(mPullTagId, &allData)) { + ALOGE("Stats puller failed for tag: %d", mPullTagId); + return; + } + for (const auto& data : allData) { + onMatchedLogEventLocked(0, *data); + } +} + void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { VLOG("Metric %lld onConditionChanged", (long long)mMetricId); @@ -326,7 +336,6 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( } void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { - mCurrentSlicedBucketForAnomaly->clear(); status_t err = NO_ERROR; for (const auto& slice : *mCurrentSlicedBucket) { if (slice.second.empty() || slice.second.front().mFields->empty()) { @@ -349,42 +358,57 @@ void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { // if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside // the GaugeMetricProducer while holding the lock. void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { - if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) { + uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + + if (eventTimeNs < currentBucketEndTimeNs) { VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs, (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs)); return; } + flushCurrentBucketLocked(eventTimeNs); + + // Adjusts the bucket start and end times. + int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, + (long long)mCurrentBucketStartTimeNs); +} + +void GaugeMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) { + uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + GaugeBucket info; info.mBucketStartNs = mCurrentBucketStartTimeNs; - info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs; + if (eventTimeNs < fullBucketEndTimeNs) { + info.mBucketEndNs = eventTimeNs; + } else { + info.mBucketEndNs = fullBucketEndTimeNs; + } info.mBucketNum = mCurrentBucketNum; for (const auto& slice : *mCurrentSlicedBucket) { info.mGaugeAtoms = slice.second; auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(info); - VLOG("gauge metric %lld, dump key value: %s", - (long long)mMetricId, slice.first.c_str()); + VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId, slice.first.c_str()); } - // Reset counters + // If we have anomaly trackers, we need to update the partial bucket values. if (mAnomalyTrackers.size() > 0) { updateCurrentSlicedBucketForAnomaly(); - for (auto& tracker : mAnomalyTrackers) { - tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum); + + if (eventTimeNs > fullBucketEndTimeNs) { + // This is known to be a full bucket, so send this data to the anomaly tracker. + for (auto& tracker : mAnomalyTrackers) { + tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum); + } + mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); } } - mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>(); - - // Adjusts the bucket start time - int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; - mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; - mCurrentBucketNum += numBucketsForward; - VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, - (long long)mCurrentBucketStartTimeNs); } size_t GaugeMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 86d0ccd241eb..d5d34be04562 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -62,6 +62,22 @@ public: // Handles when the pulled data arrives. void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override; + // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. + void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) override { + std::lock_guard<std::mutex> lock(mMutex); + + if (eventTimeNs > getCurrentBucketEndTimeNs()) { + // Flush full buckets on the normal path up to the latest bucket boundary. + flushIfNeededLocked(eventTimeNs); + } + flushCurrentBucketLocked(eventTimeNs); + mCurrentBucketStartTimeNs = eventTimeNs; + if (mPullTagId != -1) { + pullLocked(); + } + }; + protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, @@ -91,7 +107,11 @@ private: void dumpStatesLocked(FILE* out, bool verbose) const override{}; // Util function to flush the old packet. - void flushIfNeededLocked(const uint64_t& eventTime); + void flushIfNeededLocked(const uint64_t& eventTime) override; + + void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override; + + void pullLocked(); std::shared_ptr<StatsPullerManager> mStatsPullerManager; // tagId for pulled data. -1 if this is not pulled @@ -101,13 +121,15 @@ private: // TODO: Add a lock to mPastBuckets. std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets; - // The current bucket. + // The current partial bucket. std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket; - // The current bucket for anomaly detection. + // The current full bucket for anomaly detection. This is updated to the latest value seen for + // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket). std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly; - // Translate Atom based bucket to single numeric value bucket for anomaly + // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map + // for each slice with the latest value. void updateCurrentSlicedBucketForAnomaly(); // Whitelist of fields to report. Empty means all are reported. @@ -125,6 +147,8 @@ private: FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition); FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade); FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); }; diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 3b1498f07632..542dd8ab019d 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -53,15 +53,32 @@ public: virtual ~MetricProducer(){}; - void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{ - // TODO: Implement me. + /** + * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we + * first call the standard flushing code to flush up to the latest full bucket. Then we call + * the flush again when the end timestamp is forced to be now, and then after flushing, update + * the start timestamp to be now. + */ + void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) override { + std::lock_guard<std::mutex> lock(mMutex); + + if (eventTimeNs > getCurrentBucketEndTimeNs()) { + // Flush full buckets on the normal path up to the latest bucket boundary. + flushIfNeededLocked(eventTimeNs); + } + // Now flush a partial bucket. + flushCurrentBucketLocked(eventTimeNs); + mCurrentBucketStartTimeNs = eventTimeNs; + // Don't update the current bucket number so that the anomaly tracker knows this bucket + // is a partial bucket and can merge it with the previous bucket. }; - void notifyAppRemoved(const string& apk, const int uid) override{ + void notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, const int uid) override{ // TODO: Implement me. }; - void onUidMapReceived() override{ + void onUidMapReceived(const uint64_t& eventTimeNs) override{ // TODO: Implement me. }; @@ -87,11 +104,12 @@ public: }; // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. + // This method clears all the past buckets. void onDumpReport(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) { std::lock_guard<std::mutex> lock(mMutex); return onDumpReportLocked(dumpTimeNs, protoOutput); } - + // This method does not clear the past buckets. void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) { std::lock_guard<std::mutex> lock(mMutex); return onDumpReportLocked(dumpTimeNs, report); @@ -136,15 +154,43 @@ protected: virtual size_t byteSizeLocked() const = 0; virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; + /** + * Flushes the current bucket if the eventTime is after the current bucket's end time. + */ + virtual void flushIfNeededLocked(const uint64_t& eventTime){}; + + /** + * For metrics that aggregate (ie, every metric producer except for EventMetricProducer), + * we need to be able to flush the current buckets on demand (ie, end the current bucket and + * start new bucket). If this function is called when eventTimeNs is greater than the current + * bucket's end timestamp, than we flush up to the end of the latest full bucket; otherwise, + * we assume that we want to flush a partial bucket. The bucket start timestamp and bucket + * number are not changed by this function. This method should only be called by + * flushIfNeededLocked or the app upgrade handler; the caller MUST update the bucket timestamp + * and bucket number as needed. + */ + virtual void flushCurrentBucketLocked(const uint64_t& eventTimeNs){}; + + // Convenience to compute the current bucket's end time, which is always aligned with the + // start time of the metric. + uint64_t getCurrentBucketEndTimeNs() { + return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; + } + const int64_t mMetricId; const ConfigKey mConfigKey; - // The start time for the current in memory metrics data. + // The time when this metric producer was first created. The end time for the current bucket + // can be computed from this based on mCurrentBucketNum. uint64_t mStartTimeNs; + // Start time may not be aligned with the start of statsd if there is an app upgrade in the + // middle of a bucket. uint64_t mCurrentBucketStartTimeNs; + // Used by anomaly detector to track which bucket we are in. This is not sent with the produced + // report. uint64_t mCurrentBucketNum; int64_t mBucketSizeNs; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 5f0619a21a44..6573a898c55d 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -121,7 +121,8 @@ bool MetricsManager::isConfigValid() const { return mConfigValid; } -void MetricsManager::notifyAppUpgrade(const string& apk, const int uid, const int64_t version) { +void MetricsManager::notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) { // check if we care this package if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) { return; @@ -131,7 +132,8 @@ void MetricsManager::notifyAppUpgrade(const string& apk, const int uid, const in initLogSourceWhiteList(); } -void MetricsManager::notifyAppRemoved(const string& apk, const int uid) { +void MetricsManager::notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, + const int uid) { // check if we care this package if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) { return; @@ -141,7 +143,7 @@ void MetricsManager::notifyAppRemoved(const string& apk, const int uid) { initLogSourceWhiteList(); } -void MetricsManager::onUidMapReceived() { +void MetricsManager::onUidMapReceived(const uint64_t& eventTimeNs) { if (mAllowedPkg.size() == 0) { return; } diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index d4b9102d5ddc..9cae70a9de80 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -51,11 +51,12 @@ public: void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor); - void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override; + void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) override; - void notifyAppRemoved(const string& apk, const int uid) override; + void notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, const int uid) override; - void onUidMapReceived() override; + void onUidMapReceived(const uint64_t& eventTimeNs) override; bool shouldAddUidMapListener() const { return !mAllowedPkg.empty(); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index c9cc7bb9c1d4..31d9ff8455c8 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -190,7 +190,6 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, VLOG("metric %lld dump report now...", (long long)mMetricId); mPastBuckets.clear(); - mStartTimeNs = mCurrentBucketStartTimeNs; // TODO: Clear mDimensionKeyMap once the report is dumped. } @@ -320,8 +319,13 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( interval.sum += value; } + long wholeBucketVal = interval.sum; + auto prev = mCurrentFullBucket.find(eventKey); + if (prev != mCurrentFullBucket.end()) { + wholeBucketVal += prev->second; + } for (auto& tracker : mAnomalyTrackers) { - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, interval.sum); + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, wholeBucketVal); } } @@ -333,16 +337,39 @@ std::shared_ptr<FieldValueMap> ValueMetricProducer::getValueFields(const LogEven } void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { - if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) { + uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + + if (currentBucketEndTimeNs > eventTimeNs) { VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs, - (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs)); + (long long)(currentBucketEndTimeNs)); return; } + + flushCurrentBucketLocked(eventTimeNs); + + int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + + if (numBucketsForward > 1) { + VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); + } + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, + (long long)mCurrentBucketStartTimeNs); +} + +void ValueMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) { VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, (int)mCurrentSlicedBucket.size()); + uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + ValueBucket info; info.mBucketStartNs = mCurrentBucketStartTimeNs; - info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs; + if (eventTimeNs < fullBucketEndTimeNs) { + info.mBucketEndNs = eventTimeNs; + } else { + info.mBucketEndNs = fullBucketEndTimeNs; + } info.mBucketNum = mCurrentBucketNum; int tainted = 0; @@ -352,27 +379,42 @@ void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { // it will auto create new vector of ValuebucketInfo if the key is not found. auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(info); + } + VLOG("%d tainted pairs in the bucket", tainted); - for (auto& tracker : mAnomalyTrackers) { - if (tracker != nullptr) { - tracker->addPastBucket(slice.first, info.mValue, info.mBucketNum); + if (eventTimeNs > fullBucketEndTimeNs) { // If full bucket, send to anomaly tracker. + // Accumulate partial buckets with current value and then send to anomaly tracker. + if (mCurrentFullBucket.size() > 0) { + for (const auto& slice : mCurrentSlicedBucket) { + mCurrentFullBucket[slice.first] += slice.second.sum; + } + for (const auto& slice : mCurrentFullBucket) { + for (auto& tracker : mAnomalyTrackers) { + if (tracker != nullptr) { + tracker->addPastBucket(slice.first, slice.second, mCurrentBucketNum); + } + } } + mCurrentFullBucket.clear(); + } else { + // Skip aggregating the partial buckets since there's no previous partial bucket. + for (const auto& slice : mCurrentSlicedBucket) { + for (auto& tracker : mAnomalyTrackers) { + if (tracker != nullptr) { + tracker->addPastBucket(slice.first, slice.second.sum, mCurrentBucketNum); + } + } + } + } + } else { + // Accumulate partial bucket. + for (const auto& slice : mCurrentSlicedBucket) { + mCurrentFullBucket[slice.first] += slice.second.sum; } } - VLOG("%d tainted pairs in the bucket", tainted); // Reset counters mCurrentSlicedBucket.clear(); - - int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; - mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; - mCurrentBucketNum += numBucketsForward; - - if (numBucketsForward > 1) { - VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); - } - VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, - (long long)mCurrentBucketStartTimeNs); } size_t ValueMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 121ec7d18515..bf5b7dfa3ca6 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -47,6 +47,39 @@ public: void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override; + // ValueMetric needs special logic if it's a pulled atom. + void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) override { + std::lock_guard<std::mutex> lock(mMutex); + + if (mPullTagId != -1) { + vector<shared_ptr<LogEvent>> allData; + mStatsPullerManager->Pull(mPullTagId, &allData); + if (allData.size() == 0) { + // This shouldn't happen since this valuemetric is not useful now. + } + + // Pretend the pulled data occurs right before the app upgrade event. + mCondition = false; + for (const auto& data : allData) { + data->setTimestampNs(eventTimeNs - 1); + onMatchedLogEventLocked(0, *data); + } + + flushCurrentBucketLocked(eventTimeNs); + mCurrentBucketStartTimeNs = eventTimeNs; + + mCondition = true; + for (const auto& data : allData) { + data->setTimestampNs(eventTimeNs); + onMatchedLogEventLocked(0, *data); + } + } else { // For pushed value metric, we simply flush and reset the current bucket start. + flushCurrentBucketLocked(eventTimeNs); + mCurrentBucketStartTimeNs = eventTimeNs; + } + }; + protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, @@ -70,7 +103,9 @@ private: void dumpStatesLocked(FILE* out, bool verbose) const override{}; // Util function to flush the old packet. - void flushIfNeededLocked(const uint64_t& eventTime); + void flushIfNeededLocked(const uint64_t& eventTime) override; + + void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override; const FieldMatcher mValueField; @@ -101,6 +136,8 @@ private: std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket; + std::unordered_map<MetricDimensionKey, long> mCurrentFullBucket; + // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets; @@ -114,6 +151,8 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents); FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition); + FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade); + FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade); FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition); FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); }; diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 45735a866978..356a81c3e88d 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -63,7 +63,8 @@ public: DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, - uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, + uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs, + uint64_t bucketSizeNs, bool conditionSliced, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) : mConfigKey(key), mTrackerId(id), @@ -75,7 +76,9 @@ public: mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), - mCurrentBucketNum(0), + mDurationFullBucket(0), + mCurrentBucketNum(currentBucketNum), + mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), mAnomalyTrackers(anomalyTrackers){}; @@ -98,6 +101,12 @@ public: uint64_t timestampNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0; + // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from + // an app upgrade, we assume that we're trying to form a partial bucket. + virtual bool flushCurrentBucket( + const uint64_t& eventTimeNs, + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0; + // Predict the anomaly timestamp given the current status. virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const = 0; @@ -153,6 +162,13 @@ protected: } } } + + // Convenience to compute the current bucket's end time, which is always aligned with the + // start time of the metric. + uint64_t getCurrentBucketEndTimeNs() { + return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; + } + // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; @@ -172,10 +188,14 @@ protected: uint64_t mCurrentBucketStartTimeNs; - int64_t mDuration; // current recorded duration result + int64_t mDuration; // current recorded duration result (for partial bucket) + + int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket. uint64_t mCurrentBucketNum; + const uint64_t mStartTimeNs; + const bool mConditionSliced; std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index db7dea4afd04..c3bafc67adbf 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -28,11 +28,13 @@ MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, - uint64_t currentBucketStartNs, uint64_t bucketSizeNs, + uint64_t currentBucketStartNs, uint64_t currentBucketNum, + uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, - currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) { + currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, + conditionSliced, anomalyTrackers) { } unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) { @@ -102,7 +104,6 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime, bool forceStop) { - declareAnomalyIfAlarmExpired(eventTime); VLOG("MaxDuration: key %s stop", key.c_str()); if (mInfos.find(key) == mInfos.end()) { // we didn't see a start event before. do nothing. @@ -122,7 +123,7 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_ VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(), (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime); - duration.lastDuration = duration.lastDuration + durationTime; + duration.lastDuration += durationTime; VLOG(" record duration: %lld ", (long long)duration.lastDuration); } break; @@ -138,7 +139,6 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_ if (duration.lastDuration > mDuration) { mDuration = duration.lastDuration; - detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration); VLOG("Max: new max duration: %lld", (long long)mDuration); } // Once an atom duration ends, we erase it. Next time, if we see another atom event with the @@ -158,88 +158,67 @@ void MaxDurationTracker::noteStopAll(const uint64_t eventTime) { } } -bool MaxDurationTracker::flushIfNeeded( - uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) { - if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) { - return false; - } - +bool MaxDurationTracker::flushCurrentBucket( + const uint64_t& eventTimeNs, + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) { VLOG("MaxDurationTracker flushing....."); // adjust the bucket start time - int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs; - - uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs; - - DurationBucket info; - info.mBucketStartNs = mCurrentBucketStartTimeNs; - info.mBucketEndNs = endTime; - info.mBucketNum = mCurrentBucketNum; - - uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs; - mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs; + int numBucketsForward = 0; + uint64_t fullBucketEnd = getCurrentBucketEndTimeNs(); + uint64_t currentBucketEndTimeNs; + if (eventTimeNs >= fullBucketEnd) { + numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; + currentBucketEndTimeNs = fullBucketEnd; + } else { + // This must be a partial bucket. + currentBucketEndTimeNs = eventTimeNs; + } - bool hasOnGoingStartedEvent = false; // a kStarted event last across bucket boundaries. bool hasPendingEvent = false; // has either a kStarted or kPaused event across bucket boundaries - // meaning we need to carry them over to the new bucket. + // meaning we need to carry them over to the new bucket. for (auto it = mInfos.begin(); it != mInfos.end(); ++it) { int64_t finalDuration = it->second.lastDuration; - if (it->second.state == kStarted) { - // the event is still on-going, duration needs to be updated. - // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the - // duration between lastStartTime and bucketEnd. - int64_t durationTime = endTime - it->second.lastStartTime; - - finalDuration += durationTime; - VLOG(" unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration); - // if the event is still on-going, we need to fill the buckets between prev_bucket and - // now_bucket. |prev_bucket|...|..|...|now_bucket| - hasOnGoingStartedEvent = true; - } - - if (finalDuration > mDuration) { - mDuration = finalDuration; - } - if (it->second.state == DurationState::kStopped) { // No need to keep buckets for events that were stopped before. mInfos.erase(it); } else { hasPendingEvent = true; - // for kPaused, and kStarted event, we will keep track of them, and reset the start time - // and duration. - it->second.lastStartTime = mCurrentBucketStartTimeNs; - it->second.lastDuration = 0; } } + // mDuration is updated in noteStop to the maximum duration that ended in the current bucket. if (mDuration != 0) { + DurationBucket info; + info.mBucketStartNs = mCurrentBucketStartTimeNs; + info.mBucketEndNs = currentBucketEndTimeNs; + info.mBucketNum = mCurrentBucketNum; info.mDuration = mDuration; (*output)[mEventKey].push_back(info); - addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum); VLOG(" final duration for last bucket: %lld", (long long)mDuration); } - mDuration = 0; - if (hasOnGoingStartedEvent) { - for (int i = 1; i < numBucketsForward; i++) { - DurationBucket info; - info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i; - info.mBucketEndNs = endTime + mBucketSizeNs * i; - info.mBucketNum = mCurrentBucketNum + i; - info.mDuration = mBucketSizeNs; - (*output)[mEventKey].push_back(info); - addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum); - VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs); - } + if (numBucketsForward > 0) { + mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + } else { // We must be forming a partial bucket. + mCurrentBucketStartTimeNs = eventTimeNs; } - mCurrentBucketNum += numBucketsForward; + mDuration = 0; // If this tracker has no pending events, tell owner to remove. return !hasPendingEvent; } +bool MaxDurationTracker::flushIfNeeded( + uint64_t eventTimeNs, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) { + if (eventTimeNs < getCurrentBucketEndTimeNs()) { + return false; + } + return flushCurrentBucket(eventTimeNs, output); +} + void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) { // Now for each of the on-going event, check if the condition has changed for them. for (auto& pair : mInfos) { @@ -267,7 +246,6 @@ void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t times void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, const uint64_t timestamp) { - declareAnomalyIfAlarmExpired(timestamp); auto it = mInfos.find(key); if (it == mInfos.end()) { return; @@ -297,7 +275,6 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b } if (it->second.lastDuration > mDuration) { mDuration = it->second.lastDuration; - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration); } } diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 4d32a0637d56..fba4119656bd 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -28,10 +28,11 @@ namespace statsd { // they stop or bucket expires. class MaxDurationTracker : public DurationTracker { public: - MaxDurationTracker(const ConfigKey& key, const int64_t& id, - const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, - uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, + MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp<ConditionWizard> wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, + uint64_t currentBucketStartNs, uint64_t currentBucketNum, + uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); MaxDurationTracker(const MaxDurationTracker& tracker) = default; @@ -47,6 +48,9 @@ public: bool flushIfNeeded( uint64_t timestampNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; + bool flushCurrentBucket( + const uint64_t& eventTimeNs, + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>*) override; void onSlicedConditionMayChange(const uint64_t timestamp) override; void onConditionChanged(bool condition, const uint64_t timestamp) override; @@ -68,7 +72,6 @@ private: FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary); FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition); FRIEND_TEST(MaxDurationTrackerTest, TestStopAll); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 0feae369b0d5..85f7b7c4afc1 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,12 +26,13 @@ using std::pair; OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, bool conditionSliced, + sp<ConditionWizard> wizard, int conditionIndex, const FieldMatcher& dimensionInCondition, + bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum, + uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, - currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers), + currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, + conditionSliced, anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; @@ -100,7 +101,7 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint6 } if (mStarted.empty()) { mDuration += (timestamp - mLastStartTime); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration); + detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime, (long long)mDuration); } @@ -125,7 +126,7 @@ void OringDurationTracker::noteStopAll(const uint64_t timestamp) { mDuration += (timestamp - mLastStartTime); VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime, (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration); + detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); } stopAnomalyAlarm(); @@ -134,51 +135,83 @@ void OringDurationTracker::noteStopAll(const uint64_t timestamp) { mConditionKeyMap.clear(); } -bool OringDurationTracker::flushIfNeeded( - uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) { - if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) { - return false; - } +bool OringDurationTracker::flushCurrentBucket( + const uint64_t& eventTimeNs, + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) { VLOG("OringDurationTracker Flushing............."); - // adjust the bucket start time - int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs; - DurationBucket current_info; - current_info.mBucketStartNs = mCurrentBucketStartTimeNs; - current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs; - current_info.mBucketNum = mCurrentBucketNum; + + // Note that we have to mimic the bucket time changes we do in the + // MetricProducer#notifyAppUpgrade. + + int numBucketsForward = 0; + uint64_t fullBucketEnd = getCurrentBucketEndTimeNs(); + uint64_t currentBucketEndTimeNs; + + if (eventTimeNs >= fullBucketEnd) { + numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; + currentBucketEndTimeNs = fullBucketEnd; + } else { + // This must be a partial bucket. + currentBucketEndTimeNs = eventTimeNs; + } + // Process the current bucket. if (mStarted.size() > 0) { - mDuration += (current_info.mBucketEndNs - mLastStartTime); + mDuration += (currentBucketEndTimeNs - mLastStartTime); } if (mDuration > 0) { + DurationBucket current_info; + current_info.mBucketStartNs = mCurrentBucketStartTimeNs; + current_info.mBucketEndNs = currentBucketEndTimeNs; + current_info.mBucketNum = mCurrentBucketNum; current_info.mDuration = mDuration; (*output)[mEventKey].push_back(current_info); - addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum); + mDurationFullBucket += mDuration; + if (eventTimeNs > fullBucketEnd) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers(mDurationFullBucket, current_info.mBucketNum); + mDurationFullBucket = 0; + } VLOG(" duration: %lld", (long long)current_info.mDuration); } if (mStarted.size() > 0) { for (int i = 1; i < numBucketsForward; i++) { DurationBucket info; - info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i; + info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; info.mBucketNum = mCurrentBucketNum + i; info.mDuration = mBucketSizeNs; (*output)[mEventKey].push_back(info); + // Safe to send these buckets to anomaly tracker since they must be full buckets. + // If it's a partial bucket, numBucketsForward would be 0. addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum); VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); } } - mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs; - mCurrentBucketNum += numBucketsForward; - mLastStartTime = mCurrentBucketStartTimeNs; mDuration = 0; + if (numBucketsForward > 0) { + mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + } else { // We must be forming a partial bucket. + mCurrentBucketStartTimeNs = eventTimeNs; + } + mLastStartTime = mCurrentBucketStartTimeNs; + // if all stopped, then tell owner it's safe to remove this tracker. return mStarted.empty() && mPaused.empty(); } +bool OringDurationTracker::flushIfNeeded( + uint64_t eventTimeNs, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) { + if (eventTimeNs < getCurrentBucketEndTimeNs()) { + return false; + } + return flushCurrentBucket(eventTimeNs, output); +} + void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) { declareAnomalyIfAlarmExpired(timestamp); vector<pair<HashableDimensionKey, int>> startedToPaused; @@ -211,7 +244,7 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) mDuration += (timestamp - mLastStartTime); VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime), (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration); + detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); } } @@ -275,7 +308,7 @@ void OringDurationTracker::onConditionChanged(bool condition, const uint64_t tim mDuration += (timestamp - mLastStartTime); mPaused.insert(mStarted.begin(), mStarted.end()); mStarted.clear(); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration); + detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); } } if (mStarted.empty()) { @@ -298,7 +331,7 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( // As we move into the future, old buckets get overwritten (so their old data is erased). // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = mDuration; + int64_t pastNs = mDuration + mDurationFullBucket; pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey); // How much of the threshold is still unaccounted after considering pastNs. diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 75b5a815de9c..73e50e0569cb 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -30,7 +30,8 @@ public: OringDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, - uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, + uint64_t currentBucketStartNs, uint64_t currentBucketNum, + uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); OringDurationTracker(const OringDurationTracker& tracker) = default; @@ -46,6 +47,9 @@ public: void onSlicedConditionMayChange(const uint64_t timestamp) override; void onConditionChanged(bool condition, const uint64_t timestamp) override; + bool flushCurrentBucket( + const uint64_t& eventTimeNs, + std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; bool flushIfNeeded( uint64_t timestampNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h index df29eb0b76c4..03cb364e83e5 100644 --- a/cmds/statsd/src/packages/PackageInfoListener.h +++ b/cmds/statsd/src/packages/PackageInfoListener.h @@ -28,13 +28,15 @@ class PackageInfoListener : public virtual android::RefBase { public: // Uid map will notify this listener that the app with apk name and uid has been upgraded to // the specified version. - virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int64_t version) = 0; + virtual void notifyAppUpgrade(const uint64_t& eventTimeNs, const std::string& apk, + const int uid, const int64_t version) = 0; // Notify interested listeners that the given apk and uid combination no longer exits. - virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0; + virtual void notifyAppRemoved(const uint64_t& eventTimeNs, const std::string& apk, + const int uid) = 0; // Notify the listener that the UidMap snapshot is available. - virtual void onUidMapReceived() = 0; + virtual void onUidMapReceived(const uint64_t& eventTimeNs) = 0; }; } // namespace statsd diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 91279661b61f..691423e054b2 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -119,7 +119,7 @@ void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid, for (auto weakPtr : broadcastList) { auto strongPtr = weakPtr.promote(); if (strongPtr != NULL) { - strongPtr->onUidMapReceived(); + strongPtr->onUidMapReceived(timestamp); } } } @@ -166,7 +166,7 @@ void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const i for (auto weakPtr : broadcastList) { auto strongPtr = weakPtr.promote(); if (strongPtr != NULL) { - strongPtr->notifyAppUpgrade(appName, uid, versionCode); + strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode); } } } @@ -239,7 +239,7 @@ void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const i for (auto weakPtr : broadcastList) { auto strongPtr = weakPtr.promote(); if (strongPtr != NULL) { - strongPtr->notifyAppRemoved(app, uid); + strongPtr->notifyAppRemoved(timestamp, app, uid); } } } @@ -316,13 +316,13 @@ UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) { mLastUpdatePerConfigKey[key] = timestamp; int64_t newMin = getMinimumTimestampNs(); - if (newMin > prevMin) { + if (newMin > prevMin) { // Delete anything possible now that the minimum has moved forward. int64_t cutoff_nanos = newMin; auto snapshots = mOutput.mutable_snapshots(); auto it_snapshots = snapshots->cbegin(); while (it_snapshots != snapshots->cend()) { if (it_snapshots->timestamp_nanos() < cutoff_nanos) { - // it_snapshots now points to the following element. + // it_snapshots points to the following element after erasing. it_snapshots = snapshots->erase(it_snapshots); } else { ++it_snapshots; @@ -332,7 +332,7 @@ UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) { auto it_deltas = deltas->cbegin(); while (it_deltas != deltas->cend()) { if (it_deltas->timestamp_nanos() < cutoff_nanos) { - // it_deltas now points to the following element. + // it_snapshots points to the following element after erasing. it_deltas = deltas->erase(it_deltas); } else { ++it_deltas; diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 3eaf7a17a3e2..5a326a47eb24 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -275,6 +275,12 @@ message Alarm { message IncidentdDetails { repeated int32 section = 1; + + enum Destination { + AUTOMATIC = 0; + EXPLICIT = 1; + } + optional Destination dest = 2; } message PerfettoDetails { diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp new file mode 100644 index 000000000000..d9a8fc894804 --- /dev/null +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -0,0 +1,78 @@ +/* + * 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. + */ +#define DEBUG true +#include "Log.h" + +#include "IncidentdReporter.h" +#include "frameworks/base/libs/incident/proto/android/os/header.pb.h" + +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IBinder.h> +#include <binder/IServiceManager.h> + +namespace android { +namespace os { +namespace statsd { + +bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert, + const ConfigKey& configKey) { + if (config.section_size() == 0) { + VLOG("The alert %lld contains zero section in config(%d,%lld)", alert.id(), + configKey.GetUid(), (long long) configKey.GetId()); + return false; + } + + IncidentReportArgs incidentReport; + + android::os::IncidentHeaderProto header; + header.set_alert_id(alert.id()); + header.mutable_config_key()->set_uid(configKey.GetUid()); + header.mutable_config_key()->set_id(configKey.GetId()); + incidentReport.addHeader(header); + + for (int i = 0; i < config.section_size(); i++) { + incidentReport.addSection(config.section(i)); + } + + uint8_t dest; + switch (config.dest()) { + case IncidentdDetails_Destination_AUTOMATIC: + dest = android::os::DEST_AUTOMATIC; + break; + case IncidentdDetails_Destination_EXPLICIT: + dest = android::os::DEST_EXPLICIT; + break; + default: + dest = android::os::DEST_AUTOMATIC; + } + incidentReport.setDest(dest); + + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == nullptr) { + ALOGW("Failed to fetch incident service."); + return false; + } + VLOG("Calling incidentd %p", service.get()); + binder::Status s = service->reportIncident(incidentReport); + VLOG("Report incident status: %s", s.toString8().string()); + return s.isOk(); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/core/java/com/android/internal/car/ICarServiceHelper.aidl b/cmds/statsd/src/subscriber/IncidentdReporter.h index 9ee330be060b..229ed778af3a 100644 --- a/core/java/com/android/internal/car/ICarServiceHelper.aidl +++ b/cmds/statsd/src/subscriber/IncidentdReporter.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -14,11 +14,21 @@ * limitations under the License. */ -package com.android.internal.car; +#pragma once + +#include "config/ConfigKey.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert, IncidentdDetails + +namespace android { +namespace os { +namespace statsd { /** - * Helper API for car service. Only for itneraction between system server and car service. - * @hide + * Calls incidentd to trigger an incident report and put in dropbox for uploading. */ -interface ICarServiceHelper { -} +bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert, + const ConfigKey& configKey); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 1186a166dfab..a99dbe85e7a5 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -40,9 +40,11 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); // The predicate is dimensioning by any attribution node and both by uid and tag. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidAndTagDimensions( + FieldMatcher dimensions = CreateAttributionUidAndTagDimensions( android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); + // Also slice by the wakelock tag + dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; *config.add_predicate() = holdingWakelockPredicate; auto durationMetric = config.add_duration_metric(); @@ -58,131 +60,196 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) return config; } +std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"), + CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + +std::vector<AttributionNode> attributions2 = {CreateAttribution(111, "App2"), + CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + +/* +Events: +Screen off is met from (200ns,1 min+500ns]. +Acquire event for wl1 from 2ns to 1min+2ns +Acquire event for wl2 from 1min-10ns to 2min-15ns +*/ +void FeedEvents(StatsdConfig config, sp<StatsLogProcessor> processor) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + auto screenTurnedOnEvent = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1); + auto screenTurnedOffEvent = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + bucketStartTimeNs + bucketSizeNs + 500); + + auto acquireEvent1 = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); + auto releaseEvent1 = + CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2); + auto acquireEvent2 = + CreateAcquireWakelockEvent(attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10); + auto releaseEvent2 = CreateReleaseWakelockEvent(attributions2, "wl2", + bucketStartTimeNs + 2 * bucketSizeNs - 15); + + std::vector<std::unique_ptr<LogEvent>> events; + + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(acquireEvent1)); + events.push_back(std::move(acquireEvent2)); + events.push_back(std::move(releaseEvent1)); + events.push_back(std::move(releaseEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } +} + } // namespace -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration) { ConfigKey cfgKey; - for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { - auto config = CreateStatsdConfig(aggregationType); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = + auto config = CreateStatsdConfig(DurationMetric::SUM); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + // Only 1 dimension output. The tag dimension in the predicate has been aggregated. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - auto screenTurnedOnEvent = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + 1); - auto screenTurnedOffEvent = + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + android::util::WAKELOCK_STATE_CHANGED, 111); + // Validate bucket info. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // The wakelock holding interval starts from the screen off event and to the end of the 1st + // bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200); + + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + // Dump the report after the end of 2nd bucket. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + android::util::WAKELOCK_STATE_CHANGED, 111); + // Two output buckets. + // The wakelock holding interval in the 1st bucket starts from the screen off event and to + // the end of the 1st bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), + bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200)); + // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and + // ends at the second screen on event. + EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back( CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 200); - auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 500); + bucketStartTimeNs + 2 * bucketSizeNs + 90)); + events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3", + bucketStartTimeNs + 2 * bucketSizeNs + 100)); + events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3", + bucketStartTimeNs + 5 * bucketSizeNs + 100)); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), + android::util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); + EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); +} - std::vector<AttributionNode> attributions1 = - {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNode> attributions2 = - {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - auto acquireEvent1 = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 2); - auto acquireEvent2 = CreateAcquireWakelockEvent( - attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10); - - auto releaseEvent1 = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2); - auto releaseEvent2 = CreateReleaseWakelockEvent( - attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); - - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(std::move(screenTurnedOnEvent)); - events.push_back(std::move(screenTurnedOffEvent)); - events.push_back(std::move(screenTurnedOnEvent2)); - events.push_back(std::move(acquireEvent1)); - events.push_back(std::move(acquireEvent2)); - events.push_back(std::move(releaseEvent1)); - events.push_back(std::move(releaseEvent2)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - // Only 1 dimension output. The tag dimension in the predicate has been aggregated. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); - // Validate dimension value. - ValidateAttributionUidDimension( - data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, 111); - // Validate bucket info. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); - data = reports.reports(0).metrics(0).duration_metrics().data(0); - // The wakelock holding interval starts from the screen off event and to the end of the 1st - // bucket. - EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200); - - reports.Clear(); - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - // Dump the report after the end of 2nd bucket. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); - data = reports.reports(0).metrics(0).duration_metrics().data(0); - // Validate dimension value. - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111); - // Two output buckets. - // The wakelock holding interval in the 1st bucket starts from the screen off event and to - // the end of the 1st bucket. - EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), - bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200)); - // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and - // ends at the second screen on event. - EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); - - events.clear(); - events.push_back(CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs + 90)); - events.push_back(CreateAcquireWakelockEvent( - attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100)); - events.push_back(CreateReleaseWakelockEvent( - attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100)); - sortLogEventsByTimestamp(&events); - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - reports.Clear(); - processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); - data = reports.reports(0).metrics(0).duration_metrics().data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111); - // The last wakelock holding spans 4 buckets. - EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); - EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); - EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); - EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + // Nothing has ended in the first bucket. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 0); + + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + // Dump the report after the end of 2nd bucket. One dimension with one bucket. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + android::util::WAKELOCK_STATE_CHANGED, 111); + // The max is acquire event for wl1 to screen off start. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200); + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back( + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + bucketStartTimeNs + 2 * bucketSizeNs + 90)); + events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3", + bucketStartTimeNs + 2 * bucketSizeNs + 100)); + events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3", + bucketStartTimeNs + 5 * bucketSizeNs + 100)); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); } + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), + android::util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_nanos(), + bucketStartTimeNs + 5 * bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(1).end_bucket_nanos(), + bucketStartTimeNs + 6 * bucketSizeNs); } #else diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 50b3532e827a..87a1079aa722 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -191,6 +191,117 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { EXPECT_EQ(1LL, bucketInfo.mCount); } +TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + + int tagId = 1; + int conditionTagId = 2; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + Alert alert; + alert.set_num_buckets(3); + alert.set_trigger_if_sum_gt(2); + LogEvent event1(tagId, bucketStartTimeNs + 1); + event1.write("111"); // uid + event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, + bucketStartTimeNs); + sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert); + EXPECT_TRUE(anomalyTracker != nullptr); + + // Bucket is flushed yet. + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // App upgrade forces bucket flush. + // Check that there's a past bucket and the bucket end is not adjusted. + countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((long long)bucketStartTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ((long long)eventUpgradeTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + // Anomaly tracker only contains full buckets. + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + uint64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs(); + // Next event occurs in same bucket as partial bucket created. + LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10); + event2.write("222"); // uid + event2.init(); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Third event in following bucket. + LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10); + event3.write("333"); // uid + event3.init(); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); +} + +TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + + int tagId = 1; + int conditionTagId = 2; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + LogEvent event1(tagId, bucketStartTimeNs + 1); + event1.write("111"); // uid + event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, + bucketStartTimeNs); + + // Bucket is flushed yet. + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + + // App upgrade forces bucket flush. + // Check that there's a past bucket and the bucket end is not adjusted. + countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((int64_t)bucketStartTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + (uint64_t)countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + + // Next event occurs in same bucket as partial bucket created. + LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10); + event2.write("222"); // uid + event2.init(); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + + // Third event in following bucket. + LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10); + event3.write("333"); // uid + event3.init(); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((int64_t)eventUpgradeTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + (uint64_t)countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs); +} + TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { Alert alert; alert.set_id(11); diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index c9fe2523c577..23e15f78a96c 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -116,6 +116,231 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { EXPECT_EQ(1ULL, buckets2[0].mDuration); } +TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { + /** + * The duration starts from the first bucket, through the two partial buckets (10-70sec), + * another bucket, and ends at the beginning of the next full bucket. + * Expected buckets: + * - [10,25]: 14 secs + * - [25,70]: All 45 secs + * - [70,130]: All 60 secs + * - [130, 210]: Only 5 secs (event ended at 135sec) + */ + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + + durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); + EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + std::vector<DurationBucket> buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration); + EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); + buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(3UL, buckets.size()); + EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); + EXPECT_EQ(bucketSizeNs, buckets[2].mDuration); +} + +TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { + /** + * Expected buckets (start at 11s, upgrade at 75s, end at 135s): + * - [10,70]: 59 secs + * - [70,75]: 5 sec + * - [75,130]: 55 secs + */ + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + + durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); + EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + std::vector<DurationBucket> buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); + EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); + EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); + buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(3UL, buckets.size()); + EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration); +} + +TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + uint64_t startTimeNs = bucketStartTimeNs + 1; + uint64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; + + int tagId = 1; + + // Setup metric with alert. + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + Alert alert; + alert.set_num_buckets(3); + alert.set_trigger_if_sum_gt(2); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert); + EXPECT_TRUE(anomalyTracker != nullptr); + + durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); + durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); + + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, + (uint64_t)anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); +} + +TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + uint64_t startTimeNs = bucketStartTimeNs + 1; + uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); + LogEvent event1(tagId, startTimeNs); + event1.write("111"); // uid + event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + + durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); + EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); + EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); + std::vector<DurationBucket> buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(1UL, buckets.size()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); +} + +TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + uint64_t startTimeNs = bucketStartTimeNs + 1; + uint64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; + + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); + LogEvent event1(tagId, startTimeNs); + event1.write("111"); // uid + event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + + durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); + EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + // Stop occurs in the same partial bucket as created for the app upgrade. + durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); + EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + std::vector<DurationBucket> buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(1UL, buckets.size()); + EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 58be5b071b53..470d4d0415b2 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -44,6 +44,7 @@ const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000L const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; +const uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; TEST(GaugeMetricProducerTest, TestNoCondition) { GaugeMetric metric; @@ -119,6 +120,143 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } +TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_gauge_fields_filter()->set_include_all(true); + + Alert alert; + alert.set_id(101); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(25); + alert.set_num_buckets(100); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + -1 /* -1 means no pulling */, bucketStartTimeNs, + pullerManager); + sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert); + EXPECT_TRUE(anomalyTracker != nullptr); + + shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event1->write(1); + event1->write(10); + event1->init(); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); + + gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); + EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + // Partial buckets are not sent to anomaly tracker. + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Create an event in the same partial bucket. + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC); + event2->write(1); + event2->write(10); + event2->init(); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + // Partial buckets are not sent to anomaly tracker. + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Next event should trigger creation of new bucket and send previous full bucket to anomaly + // tracker. + shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC); + event3->write(1); + event3->write(10); + event3->init(); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + EXPECT_EQ(1UL, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Next event should trigger creation of new bucket. + shared_ptr<LogEvent> event4 = + make_shared<LogEvent>(tagId, bucketStartTimeNs + 125 * NS_PER_SEC); + event4->write(1); + event4->write(10); + event4->init(); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); + EXPECT_EQ(2UL, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); +} + +TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventUpgradeTimeNs); + event->write("some value"); + event->write(2); + event->init(); + data->push_back(event); + return true; + })); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + tagId, bucketStartTimeNs, pullerManager); + + vector<shared_ptr<LogEvent>> allData; + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event->write("some value"); + event->write(1); + event->init(); + allData.push_back(event); + gaugeProducer.onDataPulled(allData); + EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->second.value_int()); + + gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ((uint64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->second.value_int()); + + allData.clear(); + event = make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 1); + event->write("some value"); + event->write(3); + event->init(); + allData.push_back(event); + gaugeProducer.onDataPulled(allData); + EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->second.value_int()); +} + TEST(GaugeMetricProducerTest, TestWithCondition) { GaugeMetric metric; metric.set_id(metricId); diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 203f028080d2..2658e4ecc53c 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -41,6 +41,11 @@ const ConfigKey kConfigKey(0, 12345); const int TagId = 1; +const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); +const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; +const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); +const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); +const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); @@ -54,11 +59,13 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; int64_t metricId = 1; MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, false, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, {}); tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); // Event starts again. This would not change anything as it already starts. @@ -87,12 +94,15 @@ TEST(MaxDurationTrackerTest, TestStopAll) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; - uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; int64_t metricId = 1; MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, false, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); @@ -101,15 +111,14 @@ TEST(MaxDurationTrackerTest, TestStopAll) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets); tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40); EXPECT_TRUE(tracker.mInfos.empty()); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); EXPECT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); - - tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets); - EXPECT_EQ(2u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(40ULL, buckets[eventKey][1].mDuration); + EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); } TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { @@ -122,12 +131,15 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; - uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; int64_t metricId = 1; MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, false, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, {}); // The event starts. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -137,14 +149,18 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { ConditionKey()); // The event stops at early 4th bucket. + // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls + // flushIfneeded. tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets); tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(3u, buckets[eventKey].size()); - EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[eventKey][0].mDuration); - EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[eventKey][1].mDuration); - EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[eventKey][2].mDuration); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets); + EXPECT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); } TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { @@ -157,12 +173,15 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; - uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; int64_t metricId = 1; MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, false, {}); + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, {}); // 2 starts tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -171,21 +190,16 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/); tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets); - - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(2u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); + // Because of nesting, still not stopped. + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); // real stop now. tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets); - EXPECT_EQ(3u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); - EXPECT_EQ(5ULL, buckets[eventKey][2].mDuration); + EXPECT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration); } TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { @@ -204,14 +218,17 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; + uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t durationTimeNs = 2 * 1000; int64_t metricId = 1; MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, true, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); @@ -226,45 +243,6 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { EXPECT_EQ(5ULL, buckets[eventKey][0].mDuration); } -TEST(MaxDurationTrackerTest, TestAnomalyDetection) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); - const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - FieldMatcher dimensionInCondition; - int64_t metricId = 1; - Alert alert; - alert.set_id(101); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(32 * NS_PER_SEC); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 1; - alert.set_refractory_period_secs(refPeriodSec); - - unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; - sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - - uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; - uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; - uint64_t bucketSizeNs = 30 * NS_PER_SEC; - - sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); - - tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey()); - tracker.noteStop(key1, eventStartTimeNs + 10, false); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - EXPECT_EQ(10LL, tracker.mDuration); - - tracker.noteStart(key2, true, eventStartTimeNs + 20, ConditionKey()); - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets); - tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false); - EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration); - - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), - (eventStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3 + refPeriodSec); -} - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 80e16a1f3b55..4b579b15b16e 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -38,6 +38,12 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); const int TagId = 1; const int64_t metricId = 123; +const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); + +const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; +const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); +const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); +const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; TEST(OringDurationTrackerTest, TestDurationOverlap) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); @@ -51,13 +57,16 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; + uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, false, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -84,12 +93,15 @@ TEST(OringDurationTrackerTest, TestDurationNested) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; + uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, false, {}); + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -115,12 +127,15 @@ TEST(OringDurationTrackerTest, TestStopAll) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; + uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, false, {}); + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -145,13 +160,16 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; + uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, false, {}); + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -190,13 +208,16 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; + uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; - uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, true, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -231,12 +252,15 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; - uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; + uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t durationTimeNs = 2 * 1000; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketSizeNs, true, {}); + false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); // condition to false; record duration 5n @@ -271,11 +295,14 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; uint64_t bucketStartTimeNs = 10000000000; - uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; + uint64_t eventStartTimeNs = bucketStartTimeNs + 1; OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, true, {}); + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); @@ -311,12 +338,14 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; - uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker}); + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, {anomalyTracker}); // Nothing in the past bucket. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); @@ -379,12 +408,14 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t bucketNum = 0; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; - uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); + true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); @@ -434,8 +465,8 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, - {anomalyTracker}); + true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs, + bucketSizeNs, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 55c078dcbab8..325a372d8056 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -44,6 +44,7 @@ const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000L const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; +const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; /* * Tests pulled atoms with no conditions @@ -202,6 +203,101 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { EXPECT_EQ(false, curInterval.startUpdated); } +TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, + pullerManager); + + shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event1->write(1); + event1->write(10); + event1->init(); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + valueProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs); + + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC); + event2->write(1); + event2->write(10); + event2->init(); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs); + + // Next value should create a new bucket. + shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC); + event3->write(1); + event3->write(10); + event3->init(); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); +} + +TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + shared_ptr<MockStatsPullerManager> pullerManager = + make_shared<StrictMock<MockStatsPullerManager>>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(tagId); + event->write(120); + event->init(); + data->push_back(event); + return true; + })); + ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, tagId, bucketStartTimeNs, + pullerManager); + + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event->write(tagId); + event->write(100); + event->init(); + allData.push_back(event); + + valueProducer.onDataPulled(allData); + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + valueProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(20L, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mValue); + + allData.clear(); + event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event->write(tagId); + event->write(150); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData); + EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((uint64_t)bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(30L, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mValue); +} + TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { ValueMetric metric; metric.set_id(metricId); diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 58936fcc4452..54e971fc5ee9 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -65,6 +65,12 @@ Landroid/app/admin/DevicePolicyManager;->packageHasActiveAdmins(Ljava/lang/Strin Landroid/app/admin/DevicePolicyManager;->packageHasActiveAdmins(Ljava/lang/String;)Z Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;Z)V +Landroid/app/AlarmManager;->FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED:I +Landroid/app/AlarmManager;->FLAG_IDLE_UNTIL:I +Landroid/app/AlarmManager;->FLAG_STANDALONE:I +Landroid/app/AlarmManager;->FLAG_WAKE_FROM_IDLE:I +Landroid/app/AlarmManager;->WINDOW_EXACT:J +Landroid/app/AlarmManager;->WINDOW_HEURISTIC:J Landroid/app/AlertDialog$Builder;->P:Lcom/android/internal/app/AlertController$AlertParams; Landroid/app/AlertDialog;->mAlert:Lcom/android/internal/app/AlertController; Landroid/app/AppGlobals;->getInitialApplication()Landroid/app/Application; @@ -133,6 +139,7 @@ Landroid/app/StatusBarManager;->expandNotificationsPanel()V Landroid/app/StatusBarManager;->expandSettingsPanel(Ljava/lang/String;)V Landroid/app/StatusBarManager;->expandSettingsPanel()V Landroid/app/TimePickerDialog;->mTimePicker:Landroid/widget/TimePicker; +Landroid/app/WallpaperColors;->getColorHints()I Landroid/app/WallpaperManager;->getBitmap()Landroid/graphics/Bitmap; Landroid/app/WallpaperManager;->getBitmap(Z)Landroid/graphics/Bitmap; Landroid/app/WallpaperManager;->getIWallpaperManager()Landroid/app/IWallpaperManager; @@ -220,28 +227,13 @@ Landroid/content/pm/PackageParser;->parsePackage(Ljava/io/File;IZ)Landroid/conte Landroid/content/pm/UserInfo;->id:I Landroid/content/pm/UserInfo;->isPrimary()Z Landroid/content/res/AssetManager;->addAssetPath(Ljava/lang/String;)I -Landroid/content/res/AssetManager;->addAssetPaths([Ljava/lang/String;)[I -Landroid/content/res/AssetManager;->applyStyle(JIIJ[IIJJ)V -Landroid/content/res/AssetManager;->getArraySize(I)I Landroid/content/res/AssetManager;->getAssignedPackageIdentifiers()Landroid/util/SparseArray; -Landroid/content/res/AssetManager;->getCookieName(I)Ljava/lang/String; Landroid/content/res/AssetManager;->getResourceBagText(II)Ljava/lang/CharSequence; -Landroid/content/res/AssetManager;->loadResourceBagValue(IILandroid/util/TypedValue;Z)I -Landroid/content/res/AssetManager;->loadResourceValue(ISLandroid/util/TypedValue;Z)I -Landroid/content/res/AssetManager;->loadThemeAttributeValue(JILandroid/util/TypedValue;Z)I Landroid/content/res/AssetManager;->mObject:J -Landroid/content/res/AssetManager;->openNonAssetFdNative(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor; Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;I)Ljava/io/InputStream; Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;)Ljava/io/InputStream; Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;I)Ljava/io/InputStream; Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;)Ljava/io/InputStream; -Landroid/content/res/AssetManager;->openNonAssetNative(ILjava/lang/String;I)J -Landroid/content/res/AssetManager;->openXmlAssetNative(ILjava/lang/String;)J -Landroid/content/res/AssetManager;->resolveAttrs(JII[I[I[I[I)Z -Landroid/content/res/AssetManager;->retrieveArray(I[I)I -Landroid/content/res/AssetManager;->retrieveAttributes(J[I[I[I)Z -Landroid/content/res/AssetManager;->STYLE_NUM_ENTRIES:I -Landroid/content/res/AssetManager;->STYLE_RESOURCE_ID:I Landroid/content/res/DrawableCache;->getInstance(JLandroid/content/res/Resources;Landroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable; Landroid/content/res/Resources;->getCompatibilityInfo()Landroid/content/res/CompatibilityInfo; Landroid/content/res/ResourcesImpl;->mAccessLock:Ljava/lang/Object; @@ -271,7 +263,6 @@ Landroid/content/res/TypedArray;->mResources:Landroid/content/res/Resources; Landroid/content/res/TypedArray;->mTheme:Landroid/content/res/Resources$Theme; Landroid/content/res/TypedArray;->mValue:Landroid/util/TypedValue; Landroid/content/res/TypedArray;->mXml:Landroid/content/res/XmlBlock$Parser; -Landroid/content/res/XmlBlock;->close()V Landroid/content/res/XmlBlock;->newParser()Landroid/content/res/XmlResourceParser; Landroid/content/res/XmlBlock$Parser;->mParseState:J Landroid/content/SyncStatusInfo;->lastSuccessTime:J @@ -301,6 +292,11 @@ Landroid/graphics/drawable/StateListDrawable;->getStateDrawable(I)Landroid/graph Landroid/graphics/drawable/StateListDrawable;->getStateSet(I)[I Landroid/graphics/drawable/StateListDrawable;->mStateListState:Landroid/graphics/drawable/StateListDrawable$StateListState; Landroid/graphics/drawable/StateListDrawable;->updateStateFromTypedArray(Landroid/content/res/TypedArray;)V +Landroid/graphics/FontFamily;->abortCreation()V +Landroid/graphics/FontFamily;->addFontFromAssetManager(Landroid/content/res/AssetManager;Ljava/lang/String;IZIII[Landroid/graphics/fonts/FontVariationAxis;)Z +Landroid/graphics/FontFamily;->addFontFromBuffer(Ljava/nio/ByteBuffer;I[Landroid/graphics/fonts/FontVariationAxis;II)Z +Landroid/graphics/FontFamily;->freeze()Z +Landroid/graphics/FontFamily;-><init>()V Landroid/graphics/LinearGradient;->mColors:[I Landroid/graphics/NinePatch;->mBitmap:Landroid/graphics/Bitmap; Landroid/graphics/SurfaceTexture;->nativeDetachFromGLContext()I @@ -429,6 +425,7 @@ Landroid/os/AsyncTask;->mTaskInvoked:Ljava/util/concurrent/atomic/AtomicBoolean; Landroid/os/AsyncTask;->mWorker:Landroid/os/AsyncTask$WorkerRunnable; Landroid/os/AsyncTask;->sDefaultExecutor:Ljava/util/concurrent/Executor; Landroid/os/AsyncTask;->setDefaultExecutor(Ljava/util/concurrent/Executor;)V +Landroid/os/BatteryStats$HistoryItem;->states2:I Landroid/os/BatteryStats;->NUM_DATA_CONNECTION_TYPES:I Landroid/os/BatteryStats$Timer;->getTotalTimeLocked(JI)J Landroid/os/BatteryStats$Uid;->getFullWifiLockTime(JI)J @@ -550,13 +547,20 @@ Landroid/os/UpdateEngine;->cancel()V Landroid/os/UpdateEngine;->resetStatus()V Landroid/os/UpdateLock;->acquire()V Landroid/os/UpdateLock;->isHeld()Z +Landroid/os/UpdateLock;->NOW_IS_CONVENIENT:Ljava/lang/String; Landroid/os/UpdateLock;->release()V +Landroid/os/UpdateLock;->UPDATE_LOCK_CHANGED:Ljava/lang/String; Landroid/os/UserHandle;->ALL:Landroid/os/UserHandle; +Landroid/os/UserHandle;->getAppIdFromSharedAppGid(I)I +Landroid/os/UserHandle;->getCallingUserId()I Landroid/os/UserHandle;->getIdentifier()I Landroid/os/UserHandle;->getUserId(I)I +Landroid/os/UserHandle;-><init>(I)V Landroid/os/UserHandle;->isOwner()Z Landroid/os/UserHandle;->myUserId()I Landroid/os/UserHandle;->of(I)Landroid/os/UserHandle; +Landroid/os/UserHandle;->USER_OWNER:I +Landroid/os/UserManager;->getBadgedLabelForUser(Ljava/lang/CharSequence;Landroid/os/UserHandle;)Ljava/lang/CharSequence; Landroid/os/UserManager;->get(Landroid/content/Context;)Landroid/os/UserManager; Landroid/os/UserManager;->getMaxSupportedUsers()I Landroid/os/UserManager;->getProfiles(I)Ljava/util/List; @@ -1003,9 +1007,27 @@ Landroid/widget/VideoView;->mVideoHeight:I Landroid/widget/VideoView;->mVideoWidth:I Lcom/android/internal/app/IBatteryStats;->getStatistics()[B Lcom/android/internal/app/IBatteryStats$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IBatteryStats; +Lcom/android/internal/os/BatterySipper;->add(Lcom/android/internal/os/BatterySipper;)V +Lcom/android/internal/os/BatterySipper;->drainType:Lcom/android/internal/os/BatterySipper$DrainType; +Lcom/android/internal/os/BatterySipper;->getUid()I +Lcom/android/internal/os/BatterySipper;-><init>(Lcom/android/internal/os/BatterySipper$DrainType;Landroid/os/BatteryStats$Uid;D)V +Lcom/android/internal/os/BatterySipper;->mPackages:[Ljava/lang/String; +Lcom/android/internal/os/BatterySipper;->packageWithHighestDrain:Ljava/lang/String; +Lcom/android/internal/os/BatterySipper;->totalPowerMah:D +Lcom/android/internal/os/BatterySipper;->uidObj:Landroid/os/BatteryStats$Uid; +Lcom/android/internal/os/BatteryStatsHelper;->getMaxPower()D +Lcom/android/internal/os/BatteryStatsHelper;->getStats()Landroid/os/BatteryStats; +Lcom/android/internal/os/BatteryStatsHelper;->getTotalPower()D +Lcom/android/internal/os/BatteryStatsHelper;-><init>(Landroid/content/Context;ZZ)V +Lcom/android/internal/os/BatteryStatsHelper;->load()V +Lcom/android/internal/os/BatteryStatsHelper;->mBatteryInfo:Lcom/android/internal/app/IBatteryStats; +Lcom/android/internal/os/BatteryStatsHelper;->mPowerProfile:Lcom/android/internal/os/PowerProfile; +Lcom/android/internal/os/BatteryStatsHelper;->mUsageList:Ljava/util/List; +Lcom/android/internal/os/BatteryStatsHelper;->refreshStats(II)V Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryRealtime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryUptime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->CREATOR:Landroid/os/Parcelable$Creator; +Lcom/android/internal/os/BatteryStatsImpl;->getDischargeAmount(I)I Lcom/android/internal/os/BatteryStatsImpl;->getGlobalWifiRunningTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->getScreenOnTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->getUidStats()Landroid/util/SparseArray; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0bc510a13ba6..aca8d4819cf6 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6362,6 +6362,8 @@ public class Activity extends ContextThemeWrapper } else { writer.print(prefix); writer.println("No AutofillManager"); } + + ResourcesManager.getInstance().dump(prefix, writer); } /** diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 76a36820ed83..2ba4c00c3f46 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -88,7 +88,7 @@ public abstract class InstantAppResolverService extends Service { public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix, String token, InstantAppResolutionCallback callback) { // if not overridden, forward to old methods and filter out non-web intents - if (sanitizedIntent.isBrowsableWebIntent()) { + if (sanitizedIntent.isWebIntent()) { onGetInstantAppResolveInfo(hostDigestPrefix, token, callback); } else { callback.onInstantAppResolveInfo(Collections.emptyList()); @@ -107,7 +107,7 @@ public abstract class InstantAppResolverService extends Service { String token, InstantAppResolutionCallback callback) { Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden"); // if not overridden, forward to old methods and filter out non-web intents - if (sanitizedIntent.isBrowsableWebIntent()) { + if (sanitizedIntent.isWebIntent()) { onGetInstantAppIntentFilter(hostDigestPrefix, token, callback); } else { callback.onInstantAppResolveInfo(Collections.emptyList()); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6e4098646b27..f4836b7dbc89 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1055,11 +1055,10 @@ public class Notification implements Parcelable /** * {@link #extras} key: A * {@link android.content.ContentUris content URI} pointing to an image that can be displayed - * in the background when the notification is selected. The URI must point to an image stream - * suitable for passing into + * in the background when the notification is selected. Used on television platforms. + * The URI must point to an image stream suitable for passing into * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) - * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider - * URI used for this purpose must require no permissions to read the image data. + * BitmapFactory.decodeStream}; all other content types will be ignored. */ public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; @@ -6435,7 +6434,7 @@ public class Notification implements Parcelable public RemoteViews makeContentView(boolean increasedHeight) { mBuilder.mOriginalActions = mBuilder.mActions; mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); mBuilder.mActions = mBuilder.mOriginalActions; mBuilder.mOriginalActions = null; return remoteViews; @@ -6470,11 +6469,11 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeBigContentView(false /* showRightIcon */); + return makeMessagingView(false /* isCollapsed */); } @NonNull - private RemoteViews makeBigContentView(boolean showRightIcon) { + private RemoteViews makeMessagingView(boolean isCollapsed) { CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; @@ -6485,21 +6484,24 @@ public class Notification implements Parcelable nameReplacement = conversationTitle; conversationTitle = null; } + boolean hideLargeIcon = !isCollapsed || isOneToOne; RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null) - .hideLargeIcon(!showRightIcon || isOneToOne) + .hideLargeIcon(hideLargeIcon) .headerTextSecondary(conversationTitle) - .alwaysShowReply(showRightIcon)); + .alwaysShowReply(isCollapsed)); addExtras(mBuilder.mN.extras); // also update the end margin if there is an image int endMargin = R.dimen.notification_content_margin_end; - if (mBuilder.mN.hasLargeIcon() && showRightIcon) { + if (isCollapsed) { endMargin = R.dimen.notification_content_plus_picture_margin_end; } contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.resolveContrastColor()); + contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", + isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", @@ -6566,7 +6568,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); return remoteViews; } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 538623f26a95..b10e60870721 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -24,6 +24,7 @@ import android.annotation.TestApi; import android.app.Notification.Builder; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.drawable.Icon; import android.net.Uri; @@ -350,6 +351,14 @@ public class NotificationManager { * the same tag and id has already been posted by your application and has not yet been * canceled, it will be replaced by the updated information. * + * All {@link android.service.notification.NotificationListenerService listener services} will + * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris} + * provided on this notification or the + * {@link NotificationChannel} this notification is posted to using + * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the + * notification is canceled, or you can revoke permissions with + * {@link Context#revokeUriPermission(Uri, int)}. + * * @param tag A string identifier for this notification. May be {@code null}. * @param id An identifier for this notification. The pair (tag, id) must be unique * within your application. @@ -370,11 +379,13 @@ public class NotificationManager { String pkg = mContext.getPackageName(); // Fix the notification as best we can. Notification.addFieldsFromContext(mContext, notification); + if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } + } fixLegacySmallIcon(notification, pkg); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { @@ -385,6 +396,7 @@ public class NotificationManager { } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); notification.reduceImageSizes(mContext); + ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); boolean isLowRam = am.isLowRamDevice(); final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam); diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index fb11272d7e62..b96e028076e5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -21,6 +21,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; @@ -34,6 +35,7 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; +import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; @@ -41,9 +43,13 @@ import android.view.DisplayAdjustments; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.IndentingPrintWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -59,12 +65,7 @@ public class ResourcesManager { * Predicate that returns true if a WeakReference is gc'ed. */ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = - new Predicate<WeakReference<Resources>>() { - @Override - public boolean test(WeakReference<Resources> weakRef) { - return weakRef == null || weakRef.get() == null; - } - }; + weakRef -> weakRef == null || weakRef.get() == null; /** * The global compatibility settings. @@ -89,6 +90,48 @@ public class ResourcesManager { */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); + private static class ApkKey { + public final String path; + public final boolean sharedLib; + public final boolean overlay; + + ApkKey(String path, boolean sharedLib, boolean overlay) { + this.path = path; + this.sharedLib = sharedLib; + this.overlay = overlay; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + this.path.hashCode(); + result = 31 * result + Boolean.hashCode(this.sharedLib); + result = 31 * result + Boolean.hashCode(this.overlay); + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ApkKey)) { + return false; + } + ApkKey other = (ApkKey) obj; + return this.path.equals(other.path) && this.sharedLib == other.sharedLib + && this.overlay == other.overlay; + } + } + + /** + * The ApkAssets we are caching and intend to hold strong references to. + */ + private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15); + + /** + * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't + * in our LRU cache. Bonus resources :) + */ + private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); + /** * Resources and base configuration override associated with an Activity. */ @@ -260,6 +303,41 @@ public class ResourcesManager { } } + private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) + throws IOException { + final ApkKey newKey = new ApkKey(path, sharedLib, overlay); + ApkAssets apkAssets = mLoadedApkAssets.get(newKey); + if (apkAssets != null) { + return apkAssets; + } + + // Optimistically check if this ApkAssets exists somewhere else. + final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); + if (apkAssetsRef != null) { + apkAssets = apkAssetsRef.get(); + if (apkAssets != null) { + mLoadedApkAssets.put(newKey, apkAssets); + return apkAssets; + } else { + // Clean up the reference. + mCachedApkAssets.remove(newKey); + } + } + + // We must load this from disk. + if (overlay) { + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); + } + mLoadedApkAssets.put(newKey, apkAssets); + mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); + return apkAssets; + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -270,13 +348,15 @@ public class ResourcesManager { */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { - AssetManager assets = new AssetManager(); + final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { - if (assets.addAssetPath(key.mResDir) == 0) { + try { + apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } @@ -284,7 +364,10 @@ public class ResourcesManager { if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { - if (assets.addAssetPath(splitResDir) == 0) { + try { + apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/, + false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } @@ -293,7 +376,13 @@ public class ResourcesManager { if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { - assets.addOverlayPath(idmapPath); + try { + apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/)); + } catch (IOException e) { + Log.w(TAG, "failed to add overlay path " + idmapPath); + + // continue. + } } } @@ -302,16 +391,77 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. - if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { + try { + apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/)); + } catch (IOException e) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); + + // continue. } } } } + + AssetManager assets = new AssetManager(); + assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]), + false /*invalidateCaches*/); return assets; } + private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { + int count = 0; + for (WeakReference<T> ref : collection) { + final T value = ref != null ? ref.get() : null; + if (value != null) { + count++; + } + } + return count; + } + + /** + * @hide + */ + public void dump(String prefix, PrintWriter printWriter) { + synchronized (this) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + for (int i = 0; i < prefix.length() / 2; i++) { + pw.increaseIndent(); + } + + pw.println("ResourcesManager:"); + pw.increaseIndent(); + pw.print("cached apks: total="); + pw.print(mLoadedApkAssets.size()); + pw.print(" created="); + pw.print(mLoadedApkAssets.createCount()); + pw.print(" evicted="); + pw.print(mLoadedApkAssets.evictionCount()); + pw.print(" hit="); + pw.print(mLoadedApkAssets.hitCount()); + pw.print(" miss="); + pw.print(mLoadedApkAssets.missCount()); + pw.print(" max="); + pw.print(mLoadedApkAssets.maxSize()); + pw.println(); + + pw.print("total apks: "); + pw.println(countLiveReferences(mCachedApkAssets.values())); + + pw.print("resources: "); + + int references = countLiveReferences(mResourceReferences); + for (ActivityResources activityResources : mActivityResourceReferences.values()) { + references += countLiveReferences(activityResources.activityResources); + } + pw.println(references); + + pw.print("resource impls: "); + pw.println(countLiveReferences(mResourceImpls.values())); + } + } + private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { Configuration config; final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); @@ -630,28 +780,16 @@ public class ResourcesManager { // We will create the ResourcesImpl object outside of holding this lock. } - } - - // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. - ResourcesImpl resourcesImpl = createResourcesImpl(key); - if (resourcesImpl == null) { - return null; - } - synchronized (this) { - ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); - if (existingResourcesImpl != null) { - if (DEBUG) { - Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl - + " new impl=" + resourcesImpl); - } - resourcesImpl.getAssets().close(); - resourcesImpl = existingResourcesImpl; - } else { - // Add this ResourcesImpl to the cache. - mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. + ResourcesImpl resourcesImpl = createResourcesImpl(key); + if (resourcesImpl == null) { + return null; } + // Add this ResourcesImpl to the cache. + mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + final Resources resources; if (activityToken != null) { resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8d161006be76..77e118ccb1bf 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9404,41 +9404,6 @@ public class DevicePolicyManager { } /** - * Allows/disallows printing. - * - * Called by a device owner or a profile owner. - * Device owner changes policy for all users. Profile owner can override it if present. - * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored. - * - * @param admin which {@link DeviceAdminReceiver} this request is associated with. - * @param enabled whether printing should be allowed or not. - * @throws SecurityException if {@code admin} is neither device, nor profile owner. - */ - public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) { - try { - mService.setPrintingEnabled(admin, enabled); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Returns whether printing is enabled for this user. - * - * Always {@code false} if {@code FEATURE_PRINTING} is absent. - * Otherwise, {@code true} by default. - * - * @return {@code true} iff printing is enabled. - */ - public boolean isPrintingEnabled() { - try { - return mService.isPrintingEnabled(); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * Called by device owner to add an override APN. * * @param admin which {@link DeviceAdminReceiver} this request is associated with diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index ef990071dbfd..5218a7340ec9 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -402,9 +402,6 @@ interface IDevicePolicyManager { CharSequence getStartUserSessionMessage(in ComponentName admin); CharSequence getEndUserSessionMessage(in ComponentName admin); - void setPrintingEnabled(in ComponentName admin, boolean enabled); - boolean isPrintingEnabled(); - List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames); List<String> getMeteredDataDisabled(in ComponentName admin); diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 09f96840f918..09a5b593e521 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -676,6 +676,35 @@ public final class BluetoothSocket implements Closeable { mExcludeSdp = excludeSdp; } + /** + * Set the LE Transmit Data Length to be the maximum that the BT Controller is capable of. This + * parameter is used by the BT Controller to set the maximum transmission packet size on this + * connection. This function is currently used for testing only. + * @hide + */ + public void requestMaximumTxDataLength() throws IOException { + if (mDevice == null) { + throw new IOException("requestMaximumTxDataLength is called on null device"); + } + + try { + if (mSocketState == SocketState.CLOSED) { + throw new IOException("socket closed"); + } + IBluetooth bluetoothProxy = + BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) { + throw new IOException("Bluetooth is off"); + } + + if (DBG) Log.d(TAG, "requestMaximumTxDataLength"); + bluetoothProxy.getSocketManager().requestMaximumTxDataLength(mDevice); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + throw new IOException("unable to send RPC: " + e.getMessage()); + } + } + private String convertAddr(final byte[] addr) { return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 9b62f192ae62..fa73e3cbab99 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -10089,9 +10089,8 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ - public boolean isBrowsableWebIntent() { + public boolean isWebIntent() { return ACTION_VIEW.equals(mAction) - && hasCategory(CATEGORY_BROWSABLE) && hasWebURI(); } diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index 1a207ba055fc..8464e26ec6cd 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -16,10 +16,14 @@ package android.content.om; +import android.annotation.IntDef; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Immutable overlay information about a package. All PackageInfos that * represent an overlay package will have a corresponding OverlayInfo. @@ -27,6 +31,19 @@ import android.os.Parcelable; * @hide */ public final class OverlayInfo implements Parcelable { + + @IntDef(prefix = "STATE_", value = { + STATE_UNKNOWN, + STATE_MISSING_TARGET, + STATE_NO_IDMAP, + STATE_DISABLED, + STATE_ENABLED, + STATE_TARGET_UPGRADING, + STATE_OVERLAY_UPGRADING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + /** * An internal state used as the initial state of an overlay. OverlayInfo * objects exposed outside the {@link @@ -61,6 +78,18 @@ public final class OverlayInfo implements Parcelable { public static final int STATE_ENABLED = 3; /** + * The target package is currently being upgraded; the state will change + * once the package installation has finished. + */ + public static final int STATE_TARGET_UPGRADING = 4; + + /** + * The overlay package is currently being upgraded; the state will change + * once the package installation has finished. + */ + public static final int STATE_OVERLAY_UPGRADING = 5; + + /** * Package name of the overlay package */ public final String packageName; @@ -77,13 +106,8 @@ public final class OverlayInfo implements Parcelable { /** * The state of this OverlayInfo as defined by the STATE_* constants in this class. - * - * @see #STATE_MISSING_TARGET - * @see #STATE_NO_IDMAP - * @see #STATE_DISABLED - * @see #STATE_ENABLED */ - public final int state; + public final @State int state; /** * User handle for which this overlay applies @@ -96,13 +120,13 @@ public final class OverlayInfo implements Parcelable { * @param source the source OverlayInfo to base the new instance on * @param state the new state for the source OverlayInfo */ - public OverlayInfo(@NonNull OverlayInfo source, int state) { + public OverlayInfo(@NonNull OverlayInfo source, @State int state) { this(source.packageName, source.targetPackageName, source.baseCodePath, state, source.userId); } public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName, - @NonNull String baseCodePath, int state, int userId) { + @NonNull String baseCodePath, @State int state, int userId) { this.packageName = packageName; this.targetPackageName = targetPackageName; this.baseCodePath = baseCodePath; @@ -136,6 +160,8 @@ public final class OverlayInfo implements Parcelable { case STATE_NO_IDMAP: case STATE_DISABLED: case STATE_ENABLED: + case STATE_TARGET_UPGRADING: + case STATE_OVERLAY_UPGRADING: break; default: throw new IllegalArgumentException("State " + state + " is not a valid state"); @@ -156,7 +182,8 @@ public final class OverlayInfo implements Parcelable { dest.writeInt(userId); } - public static final Parcelable.Creator<OverlayInfo> CREATOR = new Parcelable.Creator<OverlayInfo>() { + public static final Parcelable.Creator<OverlayInfo> CREATOR = + new Parcelable.Creator<OverlayInfo>() { @Override public OverlayInfo createFromParcel(Parcel source) { return new OverlayInfo(source); @@ -189,14 +216,9 @@ public final class OverlayInfo implements Parcelable { * Translate a state to a human readable string. Only intended for * debugging purposes. * - * @see #STATE_MISSING_TARGET - * @see #STATE_NO_IDMAP - * @see #STATE_DISABLED - * @see #STATE_ENABLED - * * @return a human readable String representing the state. */ - public static String stateToString(int state) { + public static String stateToString(@State int state) { switch (state) { case STATE_UNKNOWN: return "STATE_UNKNOWN"; @@ -208,6 +230,10 @@ public final class OverlayInfo implements Parcelable { return "STATE_DISABLED"; case STATE_ENABLED: return "STATE_ENABLED"; + case STATE_TARGET_UPGRADING: + return "STATE_TARGET_UPGRADING"; + case STATE_OVERLAY_UPGRADING: + return "STATE_OVERLAY_UPGRADING"; default: return "<unknown state>"; } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index b61a6d997b14..1adc9f503f75 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1601,7 +1601,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public boolean isAllowedToUseHiddenApi() { - return false; + return isSystemApp() || isUpdatedSystemApp(); } /** diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java index 38f02256ee6e..361d4e4acb3b 100644 --- a/core/java/android/content/pm/InstantAppRequest.java +++ b/core/java/android/content/pm/InstantAppRequest.java @@ -18,12 +18,14 @@ package android.content.pm; import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; /** * Information needed to make an instant application resolution request. * @hide */ public final class InstantAppRequest { + /** Response from the first phase of instant application resolution */ public final AuxiliaryResolveInfo responseObj; /** The original intent that triggered instant application resolution */ @@ -40,6 +42,8 @@ public final class InstantAppRequest { public final Bundle verificationBundle; /** Whether resolution occurs because an application is starting */ public final boolean resolveForStart; + /** The instant app digest for this request */ + public final InstantAppResolveInfo.InstantAppDigest digest; public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, int userId, Bundle verificationBundle, @@ -51,5 +55,11 @@ public final class InstantAppRequest { this.userId = userId; this.verificationBundle = verificationBundle; this.resolveForStart = resolveForStart; + if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) { + digest = new InstantAppResolveInfo.InstantAppDigest( + origIntent.getData().getHost(), 5 /*maxDigests*/); + } else { + digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED; + } } } diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 112c5dae6731..3a95a5f87d97 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -26,10 +26,13 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Random; /** * Describes an externally resolvable instant application. There are three states that this class @@ -227,14 +230,25 @@ public final class InstantAppResolveInfo implements Parcelable { */ @SystemApi public static final class InstantAppDigest implements Parcelable { - private static final int DIGEST_MASK = 0xfffff000; - + static final int DIGEST_MASK = 0xfffff000; public static final InstantAppDigest UNDEFINED = new InstantAppDigest(new byte[][]{}, new int[]{}); + + private static Random sRandom = null; + static { + try { + sRandom = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + // oh well + sRandom = new Random(); + } + } /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; - /** The first 4 bytes of the domain hashes */ + /** The first 5 bytes of the domain hashes */ private final int[] mDigestPrefix; + /** The first 5 bytes of the domain hashes interspersed with random data */ + private int[] mDigestPrefixSecure; public InstantAppDigest(@NonNull String hostName) { this(hostName, -1 /*maxDigests*/); @@ -306,6 +320,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } mDigestPrefix = in.createIntArray(); + mDigestPrefixSecure = in.createIntArray(); } public byte[][] getDigestBytes() { @@ -316,6 +331,26 @@ public final class InstantAppResolveInfo implements Parcelable { return mDigestPrefix; } + /** + * Returns a digest prefix with additional random prefixes interspersed. + * @hide + */ + public int[] getDigestPrefixSecure() { + if (this == InstantAppResolveInfo.InstantAppDigest.UNDEFINED) { + return getDigestPrefix(); + } else if (mDigestPrefixSecure == null) { + // let's generate some random data to intersperse throughout the set of prefixes + final int realSize = getDigestPrefix().length; + final int manufacturedSize = realSize + 10 + sRandom.nextInt(10); + mDigestPrefixSecure = Arrays.copyOf(getDigestPrefix(), manufacturedSize); + for (int i = realSize; i < manufacturedSize; i++) { + mDigestPrefixSecure[i] = sRandom.nextInt() & DIGEST_MASK; + } + Arrays.sort(mDigestPrefixSecure); + } + return mDigestPrefixSecure; + } + @Override public int describeContents() { return 0; @@ -323,6 +358,11 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + final boolean isUndefined = this == UNDEFINED; + out.writeBoolean(isUndefined); + if (isUndefined) { + return; + } if (mDigestBytes == null) { out.writeInt(-1); } else { @@ -332,6 +372,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } out.writeIntArray(mDigestPrefix); + out.writeIntArray(mDigestPrefixSecure); } @SuppressWarnings("hiding") @@ -339,6 +380,9 @@ public final class InstantAppResolveInfo implements Parcelable { new Parcelable.Creator<InstantAppDigest>() { @Override public InstantAppDigest createFromParcel(Parcel in) { + if (in.readBoolean() /* is undefined */) { + return UNDEFINED; + } return new InstantAppDigest(in); } @Override diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 09a46b8acf4b..0342c93bb34f 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -468,6 +468,18 @@ public class PackageInfo implements Parcelable { dest.writeBoolean(mOverlayIsStatic); dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); + writeSigningCertificateHistoryToParcel(dest, parcelableFlags); + } + + private void writeSigningCertificateHistoryToParcel(Parcel dest, int parcelableFlags) { + if (signingCertificateHistory != null) { + dest.writeInt(signingCertificateHistory.length); + for (int i = 0; i < signingCertificateHistory.length; i++) { + dest.writeTypedArray(signingCertificateHistory[i], parcelableFlags); + } + } else { + dest.writeInt(-1); + } } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -523,6 +535,7 @@ public class PackageInfo implements Parcelable { mOverlayIsStatic = source.readBoolean(); compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); + readSigningCertificateHistoryFromParcel(source); // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. @@ -534,6 +547,16 @@ public class PackageInfo implements Parcelable { } } + private void readSigningCertificateHistoryFromParcel(Parcel source) { + int len = source.readInt(); + if (len != -1) { + signingCertificateHistory = new Signature[len][]; + for (int i = 0; i < len; i++) { + signingCertificateHistory[i] = source.createTypedArray(Signature.CREATOR); + } + } + } + private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) { if (components != null) { for (ComponentInfo ci : components) { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 3afc346af36e..377942ad31f9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -54,6 +54,7 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -196,10 +197,6 @@ public class PackageParser { private static final String TAG_RESTRICT_UPDATE = "restrict-update"; private static final String TAG_USES_SPLIT = "uses-split"; - // [b/36551762] STOPSHIP remove the ability to expose components via meta-data - // Temporary workaround; allow meta-data to expose components to instant apps - private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed"; - private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; /** @@ -1290,7 +1287,6 @@ public class PackageParser { */ @Deprecated public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { - final AssetManager assets = newConfiguredAssetManager(); final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps) { if (!lite.coreApp) { @@ -1299,8 +1295,9 @@ public class PackageParser { } } + final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { - final Package pkg = parseBaseApk(apkFile, assets, flags); + final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags); pkg.setCodePath(apkFile.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; @@ -1308,26 +1305,8 @@ public class PackageParser { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to get path: " + apkFile, e); } finally { - IoUtils.closeQuietly(assets); - } - } - - private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParserException { - if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); - } - - // The AssetManager guarantees uniqueness for asset paths, so if this asset path - // already exists in the AssetManager, addAssetPath will only return the cookie - // assigned to it. - int cookie = assets.addAssetPath(apkPath); - if (cookie == 0) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + IoUtils.closeQuietly(assetLoader); } - return cookie; } private Package parseBaseApk(File apkFile, AssetManager assets, int flags) @@ -1345,13 +1324,15 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - - Resources res = null; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); @@ -1386,15 +1367,18 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - final Resources res; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + // This must always succeed, as the path has been added to the AssetManager before. + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } + parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); @@ -1596,21 +1580,19 @@ public class PackageParser { int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); - AssetManager assets = null; XmlResourceParser parser = null; try { - assets = newConfiguredAssetManager(); - int cookie = fd != null - ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath); - if (cookie == 0) { + final ApkAssets apkAssets; + try { + apkAssets = fd != null + ? ApkAssets.loadFromFd(fd, debugPathName, false, false) + : ApkAssets.loadFromPath(apkPath); + } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath); } - final DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - - parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { @@ -1637,7 +1619,7 @@ public class PackageParser { "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); - IoUtils.closeQuietly(assets); + // TODO(b/72056911): Implement and call close() on ApkAssets. } } @@ -4430,24 +4412,6 @@ public class PackageParser { outError)) == null) { return null; } - // we don't have an attribute [or it's false], but, we have meta-data - if (!visibleToEphemeral && a.metaData.getBoolean(META_DATA_INSTANT_APPS)) { - visibleToEphemeral = true; // set in case there are more intent filters - a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; - a.info.flags &= ~ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP; - owner.visibleToInstantApps = true; - // cycle through any filters already seen - for (int i = a.intents.size() - 1; i >= 0; --i) { - a.intents.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - if (owner.preferredActivityFilters != null) { - for (int i = owner.preferredActivityFilters.size() - 1; i >= 0; --i) { - owner.preferredActivityFilters.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - } - } } else if (!receiver && parser.getName().equals("layout")) { parseLayout(res, parser, a); } else { @@ -4942,7 +4906,7 @@ public class PackageParser { p.info.authority = cpname.intern(); if (!parseProviderTags( - res, parser, visibleToEphemeral, owner, p, outError)) { + res, parser, visibleToEphemeral, p, outError)) { return null; } @@ -4950,7 +4914,7 @@ public class PackageParser { } private boolean parseProviderTags(Resources res, XmlResourceParser parser, - boolean visibleToEphemeral, Package owner, Provider outInfo, String[] outError) + boolean visibleToEphemeral, Provider outInfo, String[] outError) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); int type; @@ -4978,17 +4942,6 @@ public class PackageParser { outInfo.metaData, outError)) == null) { return false; } - // we don't have an attribute [or it's false], but, we have meta-data - if (!visibleToEphemeral && outInfo.metaData.getBoolean(META_DATA_INSTANT_APPS)) { - visibleToEphemeral = true; // set in case there are more intent filters - outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; - owner.visibleToInstantApps = true; - // cycle through any filters already seen - for (int i = outInfo.intents.size() - 1; i >= 0; --i) { - outInfo.intents.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - } } else if (parser.getName().equals("grant-uri-permission")) { TypedArray sa = res.obtainAttributes(parser, @@ -5277,17 +5230,6 @@ public class PackageParser { outError)) == null) { return null; } - // we don't have an attribute [or it's false], but, we have meta-data - if (!visibleToEphemeral && s.metaData.getBoolean(META_DATA_INSTANT_APPS)) { - visibleToEphemeral = true; // set in case there are more intent filters - s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP; - owner.visibleToInstantApps = true; - // cycle through any filters already seen - for (int i = s.intents.size() - 1; i >= 0; --i) { - s.intents.get(i) - .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); - } - } } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under <service>: " diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java index 99eb4702d32e..9e3a8f48996c 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java @@ -15,10 +15,13 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; +import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; @@ -26,6 +29,8 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; +import java.io.IOException; + /** * Loads the base and split APKs into a single AssetManager. * @hide @@ -33,68 +38,66 @@ import libcore.io.IoUtils; public class DefaultSplitAssetLoader implements SplitAssetLoader { private final String mBaseCodePath; private final String[] mSplitCodePaths; - private final int mFlags; - + private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; - public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) { + public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; mFlags = flags; } - private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParser.PackageParserException { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); } - if (assets.addAssetPath(apkPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + try { + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { if (mCachedAssetManager != null) { return mCachedAssetManager; } - AssetManager assets = new AssetManager(); - try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - loadApkIntoAssetManager(assets, mBaseCodePath, mFlags); - - if (!ArrayUtils.isEmpty(mSplitCodePaths)) { - for (String apkPath : mSplitCodePaths) { - loadApkIntoAssetManager(assets, apkPath, mFlags); - } - } + ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null + ? mSplitCodePaths.length : 0) + 1]; - mCachedAssetManager = assets; - assets = null; - return mCachedAssetManager; - } finally { - if (assets != null) { - IoUtils.closeQuietly(assets); + // Load the base. + int splitIdx = 0; + apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + + // Load any splits. + if (!ArrayUtils.isEmpty(mSplitCodePaths)) { + for (String apkPath : mSplitCodePaths) { + apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); } } + + AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + + mCachedAssetManager = assets; + return mCachedAssetManager; } @Override - public AssetManager getSplitAssetManager(int splitIdx) - throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { return getBaseAssetManager(); } @Override public void close() throws Exception { - if (mCachedAssetManager != null) { - IoUtils.closeQuietly(mCachedAssetManager); - } + IoUtils.closeQuietly(mCachedAssetManager); } } diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java index 16023f0d9d97..58eaabfa62f2 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java @@ -15,17 +15,21 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; +import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; import libcore.io.IoUtils; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -34,17 +38,15 @@ import java.util.Collections; * is to be used when an application opts-in to isolated split loading. * @hide */ -public class SplitAssetDependencyLoader - extends SplitDependencyLoader<PackageParser.PackageParserException> +public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException> implements SplitAssetLoader { private final String[] mSplitPaths; - private final int mFlags; - - private String[][] mCachedPaths; - private AssetManager[] mCachedAssetManagers; + private final @ParseFlags int mFlags; + private final ApkAssets[][] mCachedSplitApks; + private final AssetManager[] mCachedAssetManagers; public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, - SparseArray<int[]> dependencies, int flags) { + SparseArray<int[]> dependencies, @ParseFlags int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. @@ -53,7 +55,7 @@ public class SplitAssetDependencyLoader System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); mFlags = flags; - mCachedPaths = new String[mSplitPaths.length][]; + mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; mCachedAssetManagers = new AssetManager[mSplitPaths.length]; } @@ -62,58 +64,60 @@ public class SplitAssetDependencyLoader return mCachedAssetManagers[splitIdx] != null; } - private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags) - throws PackageParser.PackageParserException { - final AssetManager assets = new AssetManager(); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); + } + try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - - for (String assetPath : assetPaths) { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && - !PackageParser.isApkPath(assetPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + assetPath); - } - - if (assets.addAssetPath(assetPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + assetPath); - } - } - return assets; - } catch (Throwable e) { - IoUtils.closeQuietly(assets); - throw e; + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } + private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { + final AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + return assets; + } + @Override protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, - int parentSplitIdx) throws PackageParser.PackageParserException { - final ArrayList<String> assetPaths = new ArrayList<>(); + int parentSplitIdx) throws PackageParserException { + final ArrayList<ApkAssets> assets = new ArrayList<>(); + + // Include parent ApkAssets. if (parentSplitIdx >= 0) { - Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]); + Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); } - assetPaths.add(mSplitPaths[splitIdx]); + // Include this ApkAssets. + assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); + + // Load and include all config splits for this feature. for (int configSplitIdx : configSplitIndices) { - assetPaths.add(mSplitPaths[configSplitIdx]); + assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); } - mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]); - mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx], - mFlags); + + // Cache the results. + mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); + mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]); } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { loadDependenciesForSplit(0); return mCachedAssetManagers[0]; } @Override - public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int idx) throws PackageParserException { // Since we insert the base at position 0, and PackageParser keeps splits separate from // the base, we need to adjust the index. loadDependenciesForSplit(idx + 1); diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java new file mode 100644 index 000000000000..fd664bc731d0 --- /dev/null +++ b/core/java/android/content/res/ApkAssets.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 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.content.res; + +import android.annotation.NonNull; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * The loaded, immutable, in-memory representation of an APK. + * + * The main implementation is native C++ and there is very little API surface exposed here. The APK + * is mainly accessed via {@link AssetManager}. + * + * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, + * making the creation of AssetManagers very cheap. + * @hide + */ +public final class ApkAssets { + @GuardedBy("this") private final long mNativePtr; + @GuardedBy("this") private StringBlock mStringBlock; + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { + return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system) + throws IOException { + return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, + boolean forceSharedLibrary) throws IOException { + return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications. + * + * Performs a dup of the underlying fd, so you must take care of still closing + * the FileDescriptor yourself (and can do that whenever you want). + * + * @param fd The FileDescriptor of an open, readable APK. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLibrary) + throws IOException { + return new ApkAssets(fd, friendlyName, system, forceSharedLibrary); + } + + /** + * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path + * is encoded within the IDMAP. + * + * @param idmapPath Path to the IDMAP of an overlay APK. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) + throws IOException { + return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/); + } + + private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException { + Preconditions.checkNotNull(path, "path"); + mNativePtr = nativeLoad(path, system, forceSharedLib, overlay); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system, + boolean forceSharedLib) throws IOException { + Preconditions.checkNotNull(fd, "fd"); + Preconditions.checkNotNull(friendlyName, "friendlyName"); + mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + @NonNull String getAssetPath() { + synchronized (this) { + return nativeGetAssetPath(mNativePtr); + } + } + + CharSequence getStringFromPool(int idx) { + synchronized (this) { + return mStringBlock.get(idx); + } + } + + /** + * Retrieve a parser for a compiled XML file. This is associated with a single APK and + * <em>NOT</em> a full AssetManager. This means that shared-library references will not be + * dynamically assigned runtime package IDs. + * + * @param fileName The path to the file within the APK. + * @return An XmlResourceParser. + * @throws IOException if the file was not found or an error occurred retrieving it. + */ + public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); + synchronized (this) { + long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); + try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { + XmlResourceParser parser = block.newParser(); + // If nativeOpenXml doesn't throw, it will always return a valid native pointer, + // which makes newParser always return non-null. But let's be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } + } + } + + /** + * Returns false if the underlying APK was changed since this ApkAssets was loaded. + */ + public boolean isUpToDate() { + synchronized (this) { + return nativeIsUpToDate(mNativePtr); + } + } + + @Override + protected void finalize() throws Throwable { + nativeDestroy(mNativePtr); + } + + private static native long nativeLoad( + @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException; + private static native long nativeLoadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLib) + throws IOException; + private static native void nativeDestroy(long ptr); + private static native @NonNull String nativeGetAssetPath(long ptr); + private static native long nativeGetStringBlock(long ptr); + private static native boolean nativeIsUpToDate(long ptr); + private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 78665609bdd4..24116b42bc7b 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -18,9 +18,11 @@ package android.content.res; import android.annotation.AnyRes; import android.annotation.ArrayRes; +import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; +import android.annotation.StyleRes; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; import android.os.ParcelFileDescriptor; @@ -28,10 +30,20 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; -import java.io.FileDescriptor; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import libcore.io.IoUtils; + +import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -42,7 +54,19 @@ import java.util.HashMap; * bytes. */ public final class AssetManager implements AutoCloseable { - /* modes used when opening an asset */ + private static final String TAG = "AssetManager"; + private static final boolean DEBUG_REFS = false; + + private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk"; + + private static final Object sSync = new Object(); + + private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0]; + + // Not private for LayoutLib's BridgeAssetManager. + @GuardedBy("sSync") static AssetManager sSystem = null; + + @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0]; /** * Mode for {@link #open(String, int)}: no specific information about how @@ -65,87 +89,331 @@ public final class AssetManager implements AutoCloseable { */ public static final int ACCESS_BUFFER = 3; - private static final String TAG = "AssetManager"; - private static final boolean localLOGV = false || false; - - private static final boolean DEBUG_REFS = false; - - private static final Object sSync = new Object(); - /*package*/ static AssetManager sSystem = null; + @GuardedBy("this") private final TypedValue mValue = new TypedValue(); + @GuardedBy("this") private final long[] mOffsets = new long[2]; - private final TypedValue mValue = new TypedValue(); - private final long[] mOffsets = new long[2]; - - // For communication with native code. - private long mObject; + // Pointer to native implementation, stuffed inside a long. + @GuardedBy("this") private long mObject; + + // The loaded asset paths. + @GuardedBy("this") private ApkAssets[] mApkAssets; + + // Debug/reference counting implementation. + @GuardedBy("this") private boolean mOpen = true; + @GuardedBy("this") private int mNumRefs = 1; + @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; - private StringBlock mStringBlocks[] = null; - - private int mNumRefs = 1; - private boolean mOpen = true; - private HashMap<Long, RuntimeException> mRefStacks; - /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the * appropriate asset manager with {@link Resources#getAssets}. Not for * use by applications. - * {@hide} + * @hide */ public AssetManager() { - synchronized (this) { - if (DEBUG_REFS) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); - } - init(false); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); - ensureSystemAssets(); + final ApkAssets[] assets; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + assets = sSystemApkAssets; } - } - private static void ensureSystemAssets() { - synchronized (sSync) { - if (sSystem == null) { - AssetManager system = new AssetManager(true); - system.makeStringBlocks(null); - sSystem = system; - } + mObject = nativeCreate(); + if (DEBUG_REFS) { + mNumRefs = 0; + incRefsLocked(hashCode()); } + + // Always set the framework resources. + setApkAssets(assets, false /*invalidateCaches*/); } - - private AssetManager(boolean isSystem) { + + /** + * Private constructor that doesn't call ensureSystemAssets. + * Used for the creation of system assets. + */ + @SuppressWarnings("unused") + private AssetManager(boolean sentinel) { + mObject = nativeCreate(); if (DEBUG_REFS) { - synchronized (this) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); + mNumRefs = 0; + incRefsLocked(hashCode()); + } + } + + /** + * This must be called from Zygote so that system assets are shared by all applications. + */ + private static void createSystemAssetsInZygoteLocked() { + if (sSystem != null) { + return; + } + + // Make sure that all IDMAPs are up to date. + nativeVerifySystemIdmaps(); + + try { + ArrayList<ApkAssets> apkAssets = new ArrayList<>(); + apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/)); + loadStaticRuntimeOverlays(apkAssets); + + sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]); + sSystem = new AssetManager(true /*sentinel*/); + sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/); + } catch (IOException e) { + throw new IllegalStateException("Failed to create system AssetManager", e); + } + } + + /** + * Loads the static runtime overlays declared in /data/resource-cache/overlays.list. + * Throws an exception if the file is corrupt or if loading the APKs referenced by the file + * fails. Returns quietly if the overlays.list file doesn't exist. + * @param outApkAssets The list to fill with the loaded ApkAssets. + */ + private static void loadStaticRuntimeOverlays(ArrayList<ApkAssets> outApkAssets) + throws IOException { + final FileInputStream fis; + try { + fis = new FileInputStream("/data/resource-cache/overlays.list"); + } catch (FileNotFoundException e) { + // We might not have any overlays, this is fine. We catch here since ApkAssets + // loading can also fail with the same exception, which we would want to propagate. + Log.i(TAG, "no overlays.list file found"); + return; + } + + try { + // Acquire a lock so that any idmap scanning doesn't impact the current set. + // The order of this try-with-resources block matters. We must release the lock, and + // then close the file streams when exiting the block. + try (final BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + final FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE, true /*shared*/)) { + for (String line; (line = br.readLine()) != null; ) { + final String idmapPath = line.split(" ")[1]; + outApkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + } } + } finally { + // When BufferedReader is closed above, FileInputStream is closed as well. But let's be + // paranoid. + IoUtils.closeQuietly(fis); } - init(true); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); } /** * Return a global shared asset manager that provides access to only * system assets (no application assets). - * {@hide} + * @hide */ public static AssetManager getSystem() { - ensureSystemAssets(); - return sSystem; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + return sSystem; + } } /** * Close this asset manager. */ + @Override public void close() { - synchronized(this) { - //System.out.println("Release: num=" + mNumRefs - // + ", released=" + mReleased); + synchronized (this) { + if (!mOpen) { + return; + } + + mOpen = false; + decRefsLocked(hashCode()); + } + } + + /** + * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)} + * family of methods. + * + * @param apkAssets The new set of paths. + * @param invalidateCaches Whether to invalidate any caches. This should almost always be true. + * Set this to false if you are appending new resources + * (not new configurations). + * @hide + */ + public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) { + Preconditions.checkNotNull(apkAssets, "apkAssets"); + + // Copy the apkAssets, but prepend the system assets (framework + overlays). + final ApkAssets[] newApkAssets = new ApkAssets[apkAssets.length + sSystemApkAssets.length]; + System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length); + System.arraycopy(apkAssets, 0, newApkAssets, sSystemApkAssets.length, apkAssets.length); + + synchronized (this) { + ensureOpenLocked(); + mApkAssets = newApkAssets; + nativeSetApkAssets(mObject, mApkAssets, invalidateCaches); + if (invalidateCaches) { + // Invalidate all caches. + invalidateCachesLocked(-1); + } + } + } + + /** + * Invalidates the caches in this AssetManager according to the bitmask `diff`. + * + * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}. + * @see ActivityInfo.Config + */ + private void invalidateCachesLocked(int diff) { + // TODO(adamlesinski): Currently there are no caches to invalidate in Java code. + } + + /** + * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this + * returns a 0-length array. + * @hide + */ + public @NonNull ApkAssets[] getApkAssets() { + synchronized (this) { if (mOpen) { - mOpen = false; - decRefsLocked(this.hashCode()); + return mApkAssets; + } + } + return sEmptyApkAssets; + } + + /** + * Returns a cookie for use with the other APIs of AssetManager. + * @return 0 if the path was not found, otherwise a positive integer cookie representing + * this path in the AssetManager. + * @hide + */ + public int findCookieForPath(@NonNull String path) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (path.equals(mApkAssets[i].getAssetPath())) { + return i + 1; + } + } + } + return 0; + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPath(String path) { + return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPathAsSharedLibrary(String path) { + return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addOverlayPath(String path) { + return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); + } + + private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureOpenLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (mApkAssets[i].getAssetPath().equals(path)) { + return i + 1; + } } + + final ApkAssets assets; + try { + if (overlay) { + // TODO(b/70343104): This hardcoded path will be removed once + // addAssetPathInternal is deleted. + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib); + } + } catch (IOException e) { + return 0; + } + + final ApkAssets[] newApkAssets = Arrays.copyOf(mApkAssets, count + 1); + newApkAssets[count] = assets; + setApkAssets(newApkAssets, true); + return count + 1; + } + } + + /** + * Ensures that the native implementation has not been destroyed. + * The AssetManager may have been closed, but references to it still exist + * and therefore the native implementation is not destroyed. + */ + private void ensureValidLocked() { + if (mObject == 0) { + throw new RuntimeException("AssetManager has been destroyed"); + } + } + + /** + * Ensures that the AssetManager has not been explicitly closed. If this method passes, + * then this implies that ensureValidLocked() also passes. + */ + private void ensureOpenLocked() { + // If mOpen is true, this implies that mObject != 0. + if (!mOpen) { + throw new RuntimeException("AssetManager has been closed"); + } + } + + /** + * Populates {@code outValue} with the data associated a particular + * resource identifier for the current configuration. + * + * @param resId the resource identifier to load + * @param densityDpi the density bucket for which to load the resource + * @param outValue the typed value in which to put the data + * @param resolveRefs {@code true} to resolve references, {@code false} + * to leave them unresolved + * @return {@code true} if the data was loaded into {@code outValue}, + * {@code false} otherwise + */ + boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, + boolean resolveRefs) { + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeGetResourceValue( + mObject, resId, (short) densityDpi, outValue, resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; } } @@ -156,8 +424,7 @@ public final class AssetManager implements AutoCloseable { * @param resId the resource identifier to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceText(@StringRes int resId) { + @Nullable CharSequence getResourceText(@StringRes int resId) { synchronized (this) { final TypedValue outValue = mValue; if (getResourceValue(resId, 0, outValue, true)) { @@ -172,15 +439,15 @@ public final class AssetManager implements AutoCloseable { * identifier for the current configuration. * * @param resId the resource identifier to load - * @param bagEntryId + * @param bagEntryId the index into the bag to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { + @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { synchronized (this) { + ensureValidLocked(); final TypedValue outValue = mValue; - final int block = loadResourceBagValue(resId, bagEntryId, outValue, true); - if (block < 0) { + final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue); + if (cookie <= 0) { return null; } @@ -189,52 +456,60 @@ public final class AssetManager implements AutoCloseable { outValue.changingConfigurations); if (outValue.type == TypedValue.TYPE_STRING) { - return mStringBlocks[block].get(outValue.data); + return mApkAssets[cookie - 1].getStringFromPool(outValue.data); } return outValue.coerceToString(); } } + int getResourceArraySize(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArraySize(mObject, resId); + } + } + /** - * Retrieves the string array associated with a particular resource - * identifier for the current configuration. + * Populates `outData` with array elements of `resId`. `outData` is normally + * used with + * {@link TypedArray}. * - * @param resId the resource identifier of the string array - * @return the string array, or {@code null} + * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES} + * long, + * with the indices of the data representing the type, value, asset cookie, + * resource ID, + * configuration change mask, and density of the element. + * + * @param resId The resource ID of an array resource. + * @param outData The array to populate with data. + * @return The length of the array. + * + * @see TypedArray#STYLE_TYPE + * @see TypedArray#STYLE_DATA + * @see TypedArray#STYLE_ASSET_COOKIE + * @see TypedArray#STYLE_RESOURCE_ID + * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS + * @see TypedArray#STYLE_DENSITY */ - @Nullable - final String[] getResourceStringArray(@ArrayRes int resId) { - return getArrayStringResource(resId); + int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) { + Preconditions.checkNotNull(outData, "outData"); + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArray(mObject, resId, outData); + } } /** - * Populates {@code outValue} with the data associated a particular - * resource identifier for the current configuration. + * Retrieves the string array associated with a particular resource + * identifier for the current configuration. * - * @param resId the resource identifier to load - * @param densityDpi the density bucket for which to load the resource - * @param outValue the typed value in which to put the data - * @param resolveRefs {@code true} to resolve references, {@code false} - * to leave them unresolved - * @return {@code true} if the data was loaded into {@code outValue}, - * {@code false} otherwise + * @param resId the resource identifier of the string array + * @return the string array, or {@code null} */ - final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, - boolean resolveRefs) { + @Nullable String[] getResourceStringArray(@ArrayRes int resId) { synchronized (this) { - final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); - if (block < 0) { - return false; - } - - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); - - if (outValue.type == TypedValue.TYPE_STRING) { - outValue.string = mStringBlocks[block].get(outValue.data); - } - return true; + ensureValidLocked(); + return nativeGetResourceStringArray(mObject, resId); } } @@ -244,26 +519,48 @@ public final class AssetManager implements AutoCloseable { * * @param resId the resource id of the string array */ - final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { + @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { synchronized (this) { - final int[] rawInfoArray = getArrayStringInfo(resId); + ensureValidLocked(); + final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId); if (rawInfoArray == null) { return null; } + final int rawInfoArrayLen = rawInfoArray.length; final int infoArrayLen = rawInfoArrayLen / 2; - int block; - int index; final CharSequence[] retArray = new CharSequence[infoArrayLen]; for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) { - block = rawInfoArray[i]; - index = rawInfoArray[i + 1]; - retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null; + int cookie = rawInfoArray[i]; + int index = rawInfoArray[i + 1]; + retArray[j] = (index >= 0 && cookie > 0) + ? mApkAssets[cookie - 1].getStringFromPool(index) : null; } return retArray; } } + @Nullable int[] getResourceIntArray(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceIntArray(mObject, resId); + } + } + + /** + * Get the attributes for a style resource. These are the <item> + * elements in + * a <style> resource. + * @param resId The resource ID of the style + * @return An array of attribute IDs. + */ + @AttrRes int[] getStyleAttributes(@StyleRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetStyleAttributes(mObject, resId); + } + } + /** * Populates {@code outValue} with the data associated with a particular * resource identifier for the current configuration. Resolves theme @@ -277,73 +574,88 @@ public final class AssetManager implements AutoCloseable { * @return {@code true} if the data was loaded into {@code outValue}, * {@code false} otherwise */ - final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, + boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, boolean resolveRefs) { - final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs); - if (block < 0) { - return false; + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue, + resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; } + } - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); + void dumpTheme(long theme, int priority, String tag, String prefix) { + synchronized (this) { + ensureValidLocked(); + nativeThemeDump(mObject, theme, priority, tag, prefix); + } + } - if (outValue.type == TypedValue.TYPE_STRING) { - final StringBlock[] blocks = ensureStringBlocks(); - outValue.string = blocks[block].get(outValue.data); + @Nullable String getResourceName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceName(mObject, resId); } - return true; } - /** - * Ensures the string blocks are loaded. - * - * @return the string blocks - */ - @NonNull - final StringBlock[] ensureStringBlocks() { + @Nullable String getResourcePackageName(@AnyRes int resId) { synchronized (this) { - if (mStringBlocks == null) { - makeStringBlocks(sSystem.mStringBlocks); - } - return mStringBlocks; + ensureValidLocked(); + return nativeGetResourcePackageName(mObject, resId); } } - /*package*/ final void makeStringBlocks(StringBlock[] seed) { - final int seedNum = (seed != null) ? seed.length : 0; - final int num = getStringBlockCount(); - mStringBlocks = new StringBlock[num]; - if (localLOGV) Log.v(TAG, "Making string blocks for " + this - + ": " + num); - for (int i=0; i<num; i++) { - if (i < seedNum) { - mStringBlocks[i] = seed[i]; - } else { - mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); - } + @Nullable String getResourceTypeName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceTypeName(mObject, resId); + } + } + + @Nullable String getResourceEntryName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceEntryName(mObject, resId); } } - /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) { + @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType, + @Nullable String defPackage) { synchronized (this) { - // Cookies map to string blocks starting at 1. - return mStringBlocks[cookie - 1].get(id); + ensureValidLocked(); + // name is checked in JNI. + return nativeGetResourceIdentifier(mObject, name, defType, defPackage); } } + CharSequence getPooledStringForCookie(int cookie, int id) { + // Cookies map to ApkAssets starting at 1. + return getApkAssets()[cookie - 1].getStringFromPool(id); + } + /** * Open an asset using ACCESS_STREAMING mode. This provides access to * files that have been bundled with an application as assets -- that is, * files placed in to the "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * * @see #open(String, int) * @see #list */ - public final InputStream open(String fileName) throws IOException { + public @NonNull InputStream open(@NonNull String fileName) throws IOException { return open(fileName, ACCESS_STREAMING); } @@ -353,8 +665,7 @@ public final class AssetManager implements AutoCloseable { * with an application as assets -- that is, files placed in to the * "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * @param accessMode Desired access mode for retrieving the data. * * @see #ACCESS_UNKNOWN @@ -364,34 +675,40 @@ public final class AssetManager implements AutoCloseable { * @see #open(String) * @see #list */ - public final InputStream open(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openAsset(fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenAsset(mObject, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset file: " + fileName); } - public final AssetFileDescriptor openFd(String fileName) - throws IOException { + /** + * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides access to files that have been bundled with an application as assets -- that + * is, files placed in to the "assets" directory. + * + * The asset must be uncompressed, or an exception will be thrown. + * + * @param fileName The name of the asset to open. This name can be hierarchical. + * @return An open AssetFileDescriptor. + */ + public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset file: " + fileName); } /** @@ -406,90 +723,121 @@ public final class AssetManager implements AutoCloseable { * * @see #open */ - public native final String[] list(String path) - throws IOException; + public @Nullable String[] list(@NonNull String path) throws IOException { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + return nativeList(mObject, path); + } + } /** - * {@hide} * Open a non-asset file as an asset using ACCESS_STREAMING mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * * @see #open(String) + * @hide */ - public final InputStream openNonAsset(String fileName) throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException { return openNonAsset(0, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset file as an asset using a specific access mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * @param accessMode Desired access mode for retrieving the data. + * + * @see #ACCESS_UNKNOWN + * @see #ACCESS_STREAMING + * @see #ACCESS_RANDOM + * @see #ACCESS_BUFFER * @see #open(String, int) + * @hide */ - public final InputStream openNonAsset(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode) + throws IOException { return openNonAsset(0, fileName, accessMode); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName) + throws IOException { return openNonAsset(cookie, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. * @param accessMode Desired access mode for retrieving the data. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openNonAssetNative(cookie, fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset absolute file: " + fileName); } - public final AssetFileDescriptor openNonAssetFd(String fileName) + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName) throws IOException { return openNonAssetFd(0, fileName); } - - public final AssetFileDescriptor openNonAssetFd(int cookie, - String fileName) throws IOException { + + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param cookie Identifier of the package to be opened. + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openNonAssetFdNative(cookie, - fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = + nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset absolute file: " + fileName); } /** @@ -497,7 +845,7 @@ public final class AssetManager implements AutoCloseable { * * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(String fileName) + public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName) throws IOException { return openXmlResourceParser(0, fileName); } @@ -508,270 +856,265 @@ public final class AssetManager implements AutoCloseable { * @param cookie Identifier of the package to be opened. * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(int cookie, - String fileName) throws IOException { - XmlBlock block = openXmlBlockAsset(cookie, fileName); - XmlResourceParser rp = block.newParser(); - block.close(); - return rp; + public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName) + throws IOException { + try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) { + XmlResourceParser parser = block.newParser(); + // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with + // a valid native pointer, which makes newParser always return non-null. But let's + // be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } } /** - * {@hide} - * Retrieve a non-asset as a compiled XML file. Not for use by - * applications. + * Retrieve a non-asset as a compiled XML file. Not for use by applications. * * @param fileName The name of the file to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException { return openXmlBlockAsset(0, fileName); } /** - * {@hide} * Retrieve a non-asset as a compiled XML file. Not for use by * applications. * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long xmlBlock = openXmlAssetNative(cookie, fileName); - if (xmlBlock != 0) { - XmlBlock res = new XmlBlock(this, xmlBlock); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); + if (xmlBlock == 0) { + throw new FileNotFoundException("Asset XML file: " + fileName); } + final XmlBlock block = new XmlBlock(this, xmlBlock); + incRefsLocked(block.hashCode()); + return block; } - throw new FileNotFoundException("Asset XML file: " + fileName); } - /*package*/ void xmlBlockGone(int id) { + void xmlBlockGone(int id) { synchronized (this) { decRefsLocked(id); } } - /*package*/ final long createTheme() { + void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress, + long outIndicesAddress) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long res = newTheme(); - incRefsLocked(res); - return res; + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes, + parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress, + outIndicesAddress); + } + } + + boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues, + @NonNull int[] outIndices) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeResolveAttrs(mObject, + themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices); + } + } + + boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs, + @NonNull int[] outValues, @NonNull int[] outIndices) { + Preconditions.checkNotNull(parser, "parser"); + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeRetrieveAttributes( + mObject, parser.mParseState, inAttrs, outValues, outIndices); } } - /*package*/ final void releaseTheme(long theme) { + long createTheme() { synchronized (this) { - deleteTheme(theme); - decRefsLocked(theme); + ensureValidLocked(); + long themePtr = nativeThemeCreate(mObject); + incRefsLocked(themePtr); + return themePtr; } } + void releaseTheme(long themePtr) { + synchronized (this) { + nativeThemeDestroy(themePtr); + decRefsLocked(themePtr); + } + } + + void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) { + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeThemeApplyStyle(mObject, themePtr, resId, force); + } + } + + @Override protected void finalize() throws Throwable { - try { - if (DEBUG_REFS && mNumRefs != 0) { - Log.w(TAG, "AssetManager " + this - + " finalized with non-zero refs: " + mNumRefs); - if (mRefStacks != null) { - for (RuntimeException e : mRefStacks.values()) { - Log.w(TAG, "Reference from here", e); - } + if (DEBUG_REFS && mNumRefs != 0) { + Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs); + if (mRefStacks != null) { + for (RuntimeException e : mRefStacks.values()) { + Log.w(TAG, "Reference from here", e); } } - destroy(); - } finally { - super.finalize(); + } + + if (mObject != 0) { + nativeDestroy(mObject); } } - + + /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread + safe and it does not rely on AssetManager once it has been created. It completely owns the + underlying Asset. */ public final class AssetInputStream extends InputStream { + private long mAssetNativePtr; + private long mLength; + private long mMarkPos; + /** * @hide */ public final int getAssetInt() { throw new UnsupportedOperationException(); } + /** * @hide */ public final long getNativeAsset() { - return mAsset; + return mAssetNativePtr; } - private AssetInputStream(long asset) - { - mAsset = asset; - mLength = getAssetLength(asset); + + private AssetInputStream(long assetNativePtr) { + mAssetNativePtr = assetNativePtr; + mLength = nativeAssetGetLength(assetNativePtr); } + + @Override public final int read() throws IOException { - return readAssetChar(mAsset); - } - public final boolean markSupported() { - return true; - } - public final int available() throws IOException { - long len = getAssetRemainingLength(mAsset); - return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; + ensureOpen(); + return nativeAssetReadChar(mAssetNativePtr); } - public final void close() throws IOException { - synchronized (AssetManager.this) { - if (mAsset != 0) { - destroyAsset(mAsset); - mAsset = 0; - decRefsLocked(hashCode()); - } - } - } - public final void mark(int readlimit) { - mMarkPos = seekAsset(mAsset, 0, 0); - } - public final void reset() throws IOException { - seekAsset(mAsset, mMarkPos, -1); - } - public final int read(byte[] b) throws IOException { - return readAsset(mAsset, b, 0, b.length); + + @Override + public final int read(@NonNull byte[] b) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, 0, b.length); } - public final int read(byte[] b, int off, int len) throws IOException { - return readAsset(mAsset, b, off, len); + + @Override + public final int read(@NonNull byte[] b, int off, int len) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, off, len); } + + @Override public final long skip(long n) throws IOException { - long pos = seekAsset(mAsset, 0, 0); - if ((pos+n) > mLength) { - n = mLength-pos; + ensureOpen(); + long pos = nativeAssetSeek(mAssetNativePtr, 0, 0); + if ((pos + n) > mLength) { + n = mLength - pos; } if (n > 0) { - seekAsset(mAsset, n, 0); + nativeAssetSeek(mAssetNativePtr, n, 0); } return n; } - protected void finalize() throws Throwable - { - close(); + @Override + public final int available() throws IOException { + ensureOpen(); + final long len = nativeAssetGetRemainingLength(mAssetNativePtr); + return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len; } - private long mAsset; - private long mLength; - private long mMarkPos; - } - - /** - * Add an additional set of assets to the asset manager. This can be - * either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPath(String path) { - return addAssetPathInternal(path, false); - } - - /** - * Add an application assets to the asset manager and loading it as shared library. - * This can be either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPathAsSharedLibrary(String path) { - return addAssetPathInternal(path, true); - } - - private final int addAssetPathInternal(String path, boolean appAsLib) { - synchronized (this) { - int res = addAssetPathNative(path, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final boolean markSupported() { + return true; } - } - - private native final int addAssetPathNative(String path, boolean appAsLib); - /** - * Add an additional set of assets to the asset manager from an already open - * FileDescriptor. Not for use by applications. - * This does not give full AssetManager functionality for these assets, - * since the origin of the file is not known for purposes of sharing, - * overlay resolution, and other features. However it does allow you - * to do simple access to the contents of the given fd as an apk file. - * Performs a dup of the underlying fd, so you must take care of still closing - * the FileDescriptor yourself (and can do that whenever you want). - * Returns the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public int addAssetFd(FileDescriptor fd, String debugPathName) { - return addAssetFdInternal(fd, debugPathName, false); - } - - private int addAssetFdInternal(FileDescriptor fd, String debugPathName, - boolean appAsLib) { - synchronized (this) { - int res = addAssetFdNative(fd, debugPathName, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void mark(int readlimit) { + ensureOpen(); + mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0); } - } - - private native int addAssetFdNative(FileDescriptor fd, String debugPathName, - boolean appAsLib); - - /** - * Add a set of assets to overlay an already added set of assets. - * - * This is only intended for application resources. System wide resources - * are handled before any Java code is executed. - * - * {@hide} - */ - public final int addOverlayPath(String idmapPath) { - synchronized (this) { - int res = addOverlayPathNative(idmapPath); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void reset() throws IOException { + ensureOpen(); + nativeAssetSeek(mAssetNativePtr, mMarkPos, -1); } - } - /** - * See addOverlayPath. - * - * {@hide} - */ - public native final int addOverlayPathNative(String idmapPath); + @Override + public final void close() throws IOException { + if (mAssetNativePtr != 0) { + nativeAssetDestroy(mAssetNativePtr); + mAssetNativePtr = 0; - /** - * Add multiple sets of assets to the asset manager at once. See - * {@link #addAssetPath(String)} for more information. Returns array of - * cookies for each added asset with 0 indicating failure, or null if - * the input array of paths is null. - * {@hide} - */ - public final int[] addAssetPaths(String[] paths) { - if (paths == null) { - return null; + synchronized (AssetManager.this) { + decRefsLocked(hashCode()); + } + } } - int[] cookies = new int[paths.length]; - for (int i = 0; i < paths.length; i++) { - cookies[i] = addAssetPath(paths[i]); + @Override + protected void finalize() throws Throwable { + close(); } - return cookies; + private void ensureOpen() { + if (mAssetNativePtr == 0) { + throw new IllegalStateException("AssetInputStream is closed"); + } + } } /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to * instantiate a new AssetManager class to see the new data. - * {@hide} + * @hide */ - public native final boolean isUpToDate(); + public boolean isUpToDate() { + for (ApkAssets apkAssets : getApkAssets()) { + if (!apkAssets.isUpToDate()) { + return false; + } + } + return true; + } /** * Get the locales that this asset manager contains data for. @@ -784,7 +1127,12 @@ public final class AssetManager implements AutoCloseable { * are of the form {@code ll_CC} where {@code ll} is a two letter language code, * and {@code CC} is a two letter country code. */ - public native final String[] getLocales(); + public String[] getLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, false /*excludeSystem*/); + } + } /** * Same as getLocales(), except that locales that are only provided by the system (i.e. those @@ -794,131 +1142,57 @@ public final class AssetManager implements AutoCloseable { * assets support Cherokee and French, getLocales() would return * [Cherokee, English, French, German], while getNonSystemLocales() would return * [Cherokee, French]. - * {@hide} + * @hide */ - public native final String[] getNonSystemLocales(); - - /** {@hide} */ - public native final Configuration[] getSizeConfigurations(); + public String[] getNonSystemLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, true /*excludeSystem*/); + } + } /** - * Change the configuation used when retrieving resources. Not for use by - * applications. - * {@hide} + * @hide */ - public native final void setConfiguration(int mcc, int mnc, String locale, - int orientation, int touchscreen, int density, int keyboard, - int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, - int screenLayout, int uiMode, int colorMode, int majorVersion); + Configuration[] getSizeConfigurations() { + synchronized (this) { + ensureValidLocked(); + return nativeGetSizeConfigurations(mObject); + } + } /** - * Retrieve the resource identifier for the given resource name. + * Change the configuration used when retrieving resources. Not for use by + * applications. + * @hide */ - /*package*/ native final int getResourceIdentifier(String name, - String defType, - String defPackage); + public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation, + int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, + int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, + int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) { + synchronized (this) { + ensureValidLocked(); + nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density, + keyboard, keyboardHidden, navigation, screenWidth, screenHeight, + smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode, + colorMode, majorVersion); + } + } - /*package*/ native final String getResourceName(int resid); - /*package*/ native final String getResourcePackageName(int resid); - /*package*/ native final String getResourceTypeName(int resid); - /*package*/ native final String getResourceEntryName(int resid); - - private native final long openAsset(String fileName, int accessMode); - private final native ParcelFileDescriptor openAssetFd(String fileName, - long[] outOffsets) throws IOException; - private native final long openNonAssetNative(int cookie, String fileName, - int accessMode); - private native ParcelFileDescriptor openNonAssetFdNative(int cookie, - String fileName, long[] outOffsets) throws IOException; - private native final void destroyAsset(long asset); - private native final int readAssetChar(long asset); - private native final int readAsset(long asset, byte[] b, int off, int len); - private native final long seekAsset(long asset, long offset, int whence); - private native final long getAssetLength(long asset); - private native final long getAssetRemainingLength(long asset); - - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceValue(int ident, short density, TypedValue outValue, - boolean resolve); - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, - boolean resolve); - /*package*/ static final int STYLE_NUM_ENTRIES = 6; - /*package*/ static final int STYLE_TYPE = 0; - /*package*/ static final int STYLE_DATA = 1; - /*package*/ static final int STYLE_ASSET_COOKIE = 2; - /*package*/ static final int STYLE_RESOURCE_ID = 3; - - /* Offset within typed data array for native changingConfigurations. */ - static final int STYLE_CHANGING_CONFIGURATIONS = 4; - - /*package*/ static final int STYLE_DENSITY = 5; - /*package*/ native static final void applyStyle(long theme, - int defStyleAttr, int defStyleRes, long xmlParser, - int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress); - /*package*/ native static final boolean resolveAttrs(long theme, - int defStyleAttr, int defStyleRes, int[] inValues, - int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final boolean retrieveAttributes( - long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final int getArraySize(int resource); - /*package*/ native final int retrieveArray(int resource, int[] outValues); - private native final int getStringBlockCount(); - private native final long getNativeStringBlock(int block); - - /** - * {@hide} - */ - public native final String getCookieName(int cookie); - - /** - * {@hide} - */ - public native final SparseArray<String> getAssignedPackageIdentifiers(); - - /** - * {@hide} - */ - public native static final int getGlobalAssetCount(); - /** - * {@hide} + * @hide */ - public native static final String getAssetAllocations(); - - /** - * {@hide} - */ - public native static final int getGlobalAssetManagerCount(); - - private native final long newTheme(); - private native final void deleteTheme(long theme); - /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force); - /*package*/ native static final void copyTheme(long dest, long source); - /*package*/ native static final void clearTheme(long theme); - /*package*/ native static final int loadThemeAttributeValue(long theme, int ident, - TypedValue outValue, - boolean resolve); - /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix); - /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme); - - private native final long openXmlAssetNative(int cookie, String fileName); - - private native final String[] getArrayStringResource(int arrayRes); - private native final int[] getArrayStringInfo(int arrayRes); - /*package*/ native final int[] getArrayIntResource(int arrayRes); - /*package*/ native final int[] getStyleAttributes(int themeRes); - - private native final void init(boolean isSystem); - private native final void destroy(); - - private final void incRefsLocked(long id) { + public SparseArray<String> getAssignedPackageIdentifiers() { + synchronized (this) { + ensureValidLocked(); + return nativeGetAssignedPackageIdentifiers(mObject); + } + } + + private void incRefsLocked(long id) { if (DEBUG_REFS) { if (mRefStacks == null) { - mRefStacks = new HashMap<Long, RuntimeException>(); + mRefStacks = new HashMap<>(); } RuntimeException ex = new RuntimeException(); ex.fillInStackTrace(); @@ -926,16 +1200,118 @@ public final class AssetManager implements AutoCloseable { } mNumRefs++; } - - private final void decRefsLocked(long id) { + + private void decRefsLocked(long id) { if (DEBUG_REFS && mRefStacks != null) { mRefStacks.remove(id); } mNumRefs--; - //System.out.println("Dec streams: mNumRefs=" + mNumRefs - // + " mReleased=" + mReleased); - if (mNumRefs == 0) { - destroy(); + if (mNumRefs == 0 && mObject != 0) { + nativeDestroy(mObject); + mObject = 0; + mApkAssets = sEmptyApkAssets; } } + + // AssetManager setup native methods. + private static native long nativeCreate(); + private static native void nativeDestroy(long ptr); + private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets, + boolean invalidateCaches); + private static native void nativeSetConfiguration(long ptr, int mcc, int mnc, + @Nullable String locale, int orientation, int touchscreen, int density, int keyboard, + int keyboardHidden, int navigation, int screenWidth, int screenHeight, + int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, + int uiMode, int colorMode, int majorVersion); + private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers( + long ptr); + + // File native methods. + private static native @Nullable String[] nativeList(long ptr, @NonNull String path) + throws IOException; + private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode); + private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr, + @NonNull String fileName, long[] outOffsets) throws IOException; + private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName, + int accessMode); + private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie, + @NonNull String fileName, @NonNull long[] outOffsets) throws IOException; + private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName); + + // Primitive resource native methods. + private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density, + @NonNull TypedValue outValue, boolean resolveReferences); + private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId, + @NonNull TypedValue outValue); + + private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr, + @StyleRes int resId); + private static native @Nullable String[] nativeGetResourceStringArray(long ptr, + @ArrayRes int resId); + private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr, + @ArrayRes int resId); + private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId); + private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId); + private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId, + @NonNull int[] outValues); + + // Resource name/ID native methods. + private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name, + @Nullable String defType, @Nullable String defPackage); + private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid); + private static native @Nullable String nativeGetResourcePackageName(long ptr, + @AnyRes int resid); + private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid); + private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid); + private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem); + private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr); + + // Style attribute retrieval native methods. + private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr, + @StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs, + long outValuesAddress, long outIndicesAddress); + private static native boolean nativeResolveAttrs(long ptr, long themePtr, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues, + @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices); + private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr, + @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices); + + // Theme related native methods + private static native long nativeThemeCreate(long ptr); + private static native void nativeThemeDestroy(long themePtr); + private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId, + boolean force); + static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr); + static native void nativeThemeClear(long themePtr); + private static native int nativeThemeGetAttributeValue(long ptr, long themePtr, + @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve); + private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag, + String prefix); + static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr); + + // AssetInputStream related native methods. + private static native void nativeAssetDestroy(long assetPtr); + private static native int nativeAssetReadChar(long assetPtr); + private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len); + private static native long nativeAssetSeek(long assetPtr, long offset, int whence); + private static native long nativeAssetGetLength(long assetPtr); + private static native long nativeAssetGetRemainingLength(long assetPtr); + + private static native void nativeVerifySystemIdmaps(); + + // Global debug native methods. + /** + * @hide + */ + public static native int getGlobalAssetCount(); + + /** + * @hide + */ + public static native String getAssetAllocations(); + + /** + * @hide + */ + public static native int getGlobalAssetManagerCount(); } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index e173653cd961..8f58891ed556 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -590,7 +590,7 @@ public class Resources { */ @NonNull public int[] getIntArray(@ArrayRes int id) throws NotFoundException { - int[] res = mResourcesImpl.getAssets().getArrayIntResource(id); + int[] res = mResourcesImpl.getAssets().getResourceIntArray(id); if (res != null) { return res; } @@ -613,13 +613,13 @@ public class Resources { @NonNull public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException { final ResourcesImpl impl = mResourcesImpl; - int len = impl.getAssets().getArraySize(id); + int len = impl.getAssets().getResourceArraySize(id); if (len < 0) { throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); } TypedArray array = TypedArray.obtain(this, len); - array.mLength = impl.getAssets().retrieveArray(id, array.mData); + array.mLength = impl.getAssets().getResourceArray(id, array.mData); array.mIndices[0] = 0; return array; @@ -1789,8 +1789,7 @@ public class Resources { // out the attributes from the XML file (applying type information // contained in the resources and such). XmlBlock.Parser parser = (XmlBlock.Parser)set; - mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs, - array.mData, array.mIndices); + mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices); array.mXml = parser; diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 97cb78bc4243..80e3860bfb9e 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -27,9 +27,11 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; +import android.content.res.AssetManager.AssetInputStream; import android.content.res.Configuration.NativeConfig; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; +import android.graphics.ImageDecoder; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -168,7 +170,6 @@ public class ResourcesImpl { mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo()); - mAssets.ensureStringBlocks(); } public DisplayAdjustments getDisplayAdjustments() { @@ -809,8 +810,13 @@ public class ResourcesImpl { } else { final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); - dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); - is.close(); + AssetInputStream ais = (AssetInputStream) is; + // ImageDecoder will close the input stream. + ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais, + wrapper, value); + dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> { + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + }); } } finally { stack.pop(); @@ -1274,8 +1280,7 @@ public class ResourcesImpl { void applyStyle(int resId, boolean force) { synchronized (mKey) { - AssetManager.applyThemeStyle(mTheme, resId, force); - + mAssets.applyStyleToTheme(mTheme, resId, force); mThemeResId = resId; mKey.append(resId, force); } @@ -1284,7 +1289,7 @@ public class ResourcesImpl { void setTo(ThemeImpl other) { synchronized (mKey) { synchronized (other.mKey) { - AssetManager.copyTheme(mTheme, other.mTheme); + AssetManager.nativeThemeCopy(mTheme, other.mTheme); mThemeResId = other.mThemeResId; mKey.setTo(other.getKey()); @@ -1307,12 +1312,10 @@ public class ResourcesImpl { // out the attributes from the XML file (applying type information // contained in the resources and such). final XmlBlock.Parser parser = (XmlBlock.Parser) set; - AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, - parser != null ? parser.mParseState : 0, - attrs, attrs.length, array.mDataAddress, array.mIndicesAddress); + mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs, + array.mDataAddress, array.mIndicesAddress); array.mTheme = wrapper; array.mXml = parser; - return array; } } @@ -1329,7 +1332,7 @@ public class ResourcesImpl { } final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); - AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); + mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); array.mTheme = wrapper; array.mXml = null; return array; @@ -1349,14 +1352,14 @@ public class ResourcesImpl { @Config int getChangingConfigurations() { synchronized (mKey) { final @NativeConfig int nativeChangingConfig = - AssetManager.getThemeChangingConfigurations(mTheme); + AssetManager.nativeThemeGetChangingConfigurations(mTheme); return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); } } public void dump(int priority, String tag, String prefix) { synchronized (mKey) { - AssetManager.dumpTheme(mTheme, priority, tag, prefix); + mAssets.dumpTheme(mTheme, priority, tag, prefix); } } @@ -1385,13 +1388,13 @@ public class ResourcesImpl { */ void rebase() { synchronized (mKey) { - AssetManager.clearTheme(mTheme); + AssetManager.nativeThemeClear(mTheme); // Reapply the same styles in the same order. for (int i = 0; i < mKey.mCount; i++) { final int resId = mKey.mResId[i]; final boolean force = mKey.mForce[i]; - AssetManager.applyThemeStyle(mTheme, resId, force); + mAssets.applyStyleToTheme(mTheme, resId, force); } } } diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index f33c75168a5f..cbb3c6df0558 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -61,6 +61,15 @@ public class TypedArray { return attrs; } + // STYLE_ prefixed constants are offsets within the typed data array. + static final int STYLE_NUM_ENTRIES = 6; + static final int STYLE_TYPE = 0; + static final int STYLE_DATA = 1; + static final int STYLE_ASSET_COOKIE = 2; + static final int STYLE_RESOURCE_ID = 3; + static final int STYLE_CHANGING_CONFIGURATIONS = 4; + static final int STYLE_DENSITY = 5; + private final Resources mResources; private DisplayMetrics mMetrics; private AssetManager mAssets; @@ -78,7 +87,7 @@ public class TypedArray { private void resize(int len) { mLength = len; - final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES; + final int dataLen = len * STYLE_NUM_ENTRIES; final int indicesLen = len + 1; final VMRuntime runtime = VMRuntime.getRuntime(); if (mDataAddress == 0 || mData.length < dataLen) { @@ -166,9 +175,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -203,9 +212,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -242,14 +251,13 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_STRING) { - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]).toString(); + return mXml.getPooledString(data[index + STYLE_DATA]).toString(); } } return null; @@ -274,11 +282,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); if ((changingConfigs & ~allowedChangingConfigs) != 0) { return null; } @@ -320,14 +328,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA] != 0; + return data[index + STYLE_DATA] != 0; } final TypedValue v = mValue; @@ -359,14 +367,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -396,16 +404,16 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FLOAT) { - return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); + return Float.intBitsToFloat(data[index + STYLE_DATA]); } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -446,15 +454,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; if (getValueAt(index, value)) { @@ -498,7 +506,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -533,7 +541,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -564,15 +572,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -612,15 +620,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimension( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -661,15 +668,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelOffset( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -711,15 +717,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -755,16 +760,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -795,15 +799,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } return defValue; @@ -833,15 +836,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FRACTION) { - return TypedValue.complexToFraction( - data[index+AssetManager.STYLE_DATA], base, pbase); + return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -874,10 +876,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) { - final int resid = data[index+AssetManager.STYLE_RESOURCE_ID]; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) { + final int resid = data[index + STYLE_RESOURCE_ID]; if (resid != 0) { return resid; } @@ -902,10 +904,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { - return data[index + AssetManager.STYLE_DATA]; + if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { + return data[index + STYLE_DATA]; } return defValue; } @@ -939,7 +941,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -975,7 +977,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -1006,7 +1008,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return mResources.getTextArray(value.resourceId); } return null; @@ -1027,7 +1029,7 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue); + return getValueAt(index * STYLE_NUM_ENTRIES, outValue); } /** @@ -1043,8 +1045,8 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; - return mData[index + AssetManager.STYLE_TYPE]; + index *= STYLE_NUM_ENTRIES; + return mData[index + STYLE_TYPE]; } /** @@ -1063,9 +1065,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL; } @@ -1084,11 +1086,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL - || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; + || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; } /** @@ -1109,7 +1111,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return value; } return null; @@ -1181,16 +1183,16 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { + final int index = i * STYLE_NUM_ENTRIES; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { // Not an attribute, ignore. continue; } // Null the entry so that we can safely call getZzz(). - data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL; + data[index + STYLE_TYPE] = TypedValue.TYPE_NULL; - final int attr = data[index + AssetManager.STYLE_DATA]; + final int attr = data[index + STYLE_DATA]; if (attr == 0) { // Useless data, ignore. continue; @@ -1231,45 +1233,44 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - final int type = data[index + AssetManager.STYLE_TYPE]; + final int index = i * STYLE_NUM_ENTRIES; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { continue; } changingConfig |= ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); } return changingConfig; } private boolean getValueAt(int index, TypedValue outValue) { final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return false; } outValue.type = type; - outValue.data = data[index+AssetManager.STYLE_DATA]; - outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; - outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID]; + outValue.data = data[index + STYLE_DATA]; + outValue.assetCookie = data[index + STYLE_ASSET_COOKIE]; + outValue.resourceId = data[index + STYLE_RESOURCE_ID]; outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); - outValue.density = data[index+AssetManager.STYLE_DENSITY]; + data[index + STYLE_CHANGING_CONFIGURATIONS]); + outValue.density = data[index + STYLE_DENSITY]; outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null; return true; } private CharSequence loadStringValueAt(int index) { final int[] data = mData; - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { if (mXml != null) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]); + return mXml.getPooledString(data[index + STYLE_DATA]); } return null; } - return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]); + return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]); } /** @hide */ diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index e6b957414ea8..d4ccffb83ca5 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -16,6 +16,7 @@ package android.content.res; +import android.annotation.Nullable; import android.util.TypedValue; import com.android.internal.util.XmlUtils; @@ -33,7 +34,7 @@ import java.io.Reader; * * {@hide} */ -final class XmlBlock { +final class XmlBlock implements AutoCloseable { private static final boolean DEBUG=false; public XmlBlock(byte[] data) { @@ -48,6 +49,7 @@ final class XmlBlock { mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } + @Override public void close() { synchronized (this) { if (mOpen) { @@ -478,13 +480,13 @@ final class XmlBlock { * are doing! The given native object must exist for the entire lifetime * of this newly creating XmlBlock. */ - XmlBlock(AssetManager assets, long xmlBlock) { + XmlBlock(@Nullable AssetManager assets, long xmlBlock) { mAssets = assets; mNative = xmlBlock; mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false); } - private final AssetManager mAssets; + private @Nullable final AssetManager mAssets; private final long mNative; /*package*/ final StringBlock mStrings; private boolean mOpen = true; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index c1c0812e129e..ae1f57d62228 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -2006,7 +2006,6 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory, * SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING, * myDatabaseErrorHandler); - * db.enableWriteAheadLogging(); * </pre></code> * </p><p> * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging} diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index bae2d04b0a24..0be45a0c9779 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -17,7 +17,6 @@ package android.hardware.camera2; import android.annotation.NonNull; -import android.annotation.Nullable; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; import android.hardware.camera2.impl.PhysicalCaptureResultInfo; @@ -26,6 +25,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * <p>The total assembled results of a single image capture from the image sensor.</p> @@ -49,9 +49,9 @@ import java.util.List; * * <p>For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying * physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata - * for that physical camera. And its keys and values can be accessed by - * {@link #getPhysicalCameraKey}. If all requested surfaces are for the logical camera, no - * metadata for physical camera will be included.</p> + * for that physical camera. And the mapping between the physical camera id and result metadata + * can be accessed via {@link #getPhysicalCameraResults}. If all requested surfaces are for the + * logical camera, no metadata for physical camera will be included.</p> * * <p>{@link TotalCaptureResult} objects are immutable.</p> * @@ -62,7 +62,7 @@ public final class TotalCaptureResult extends CaptureResult { private final List<CaptureResult> mPartialResults; private final int mSessionId; // The map between physical camera id and capture result - private final HashMap<String, CameraMetadataNative> mPhysicalCaptureResults; + private final HashMap<String, CaptureResult> mPhysicalCaptureResults; /** * Takes ownership of the passed-in camera metadata and the partial results @@ -83,10 +83,12 @@ public final class TotalCaptureResult extends CaptureResult { mSessionId = sessionId; - mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>(); + mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { + CaptureResult physicalResult = new CaptureResult( + onePhysicalResult.getCameraMetadata(), parent, extras); mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), - onePhysicalResult.getCameraMetadata()); + physicalResult); } } @@ -101,7 +103,7 @@ public final class TotalCaptureResult extends CaptureResult { mPartialResults = new ArrayList<>(); mSessionId = CameraCaptureSession.SESSION_ID_NONE; - mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>(); + mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); } /** @@ -132,36 +134,20 @@ public final class TotalCaptureResult extends CaptureResult { } /** - * Get a capture result field value for a particular physical camera id. + * Get the map between physical camera ids and their capture result metadata * - * <p>The field definitions can be found in {@link CaptureResult}.</p> - * - * <p>This function can be called for logical camera devices, which are devices that have + * <p>This function can be called for logical multi-camera devices, which are devices that have * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link * CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of physical devices that - * are backing the logical camera. The camera id included in physicalCameraId argument - * selects an individual physical device, and returns its specific capture result field.</p> - * - * <p>This function should only be called if one or more streams from the underlying - * 'physicalCameraId' was requested by the corresponding capture request.</p> - * - * @throws IllegalArgumentException if the key was not valid, or the physicalCameraId is not - * applicable to the current camera, or a stream from 'physicalCameraId' is not requested by the - * corresponding capture request. - * - * @param key The result field to read. - * @param physicalCameraId The physical camera the result originates from. + * are backing the logical camera.</p> * - * @return The value of that key, or {@code null} if the field is not set. - */ - @Nullable - public <T> T getPhysicalCameraKey(Key<T> key, @NonNull String physicalCameraId) { - if (!mPhysicalCaptureResults.containsKey(physicalCameraId)) { - throw new IllegalArgumentException( - "No TotalCaptureResult exists for physical camera " + physicalCameraId); - } + * <p>If one or more streams from the underlying physical cameras were requested by the + * corresponding capture request, this function returns the result metadata for those physical + * cameras. Otherwise, an empty map is returned.</p> - CameraMetadataNative physicalMetadata = mPhysicalCaptureResults.get(physicalCameraId); - return physicalMetadata.get(key); + * @return unmodifiable map between physical camera ids and their capture result metadata + */ + public Map<String, CaptureResult> getPhysicalCameraResults() { + return Collections.unmodifiableMap(mPhysicalCaptureResults); } } diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index eb4bcededbf2..b205e2c7649d 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -368,7 +368,7 @@ public final class OutputConfiguration implements Parcelable { * desirable for the camera application to request streams from individual physical cameras. * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p> * - * <p>The valid physical camera id can be queried by {@link + * <p>The valid physical camera ids can be queried by {@link * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. * </p> * diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index 287bdc88dd3e..74d64704c8d2 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -26,6 +26,7 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Random; @@ -329,16 +330,34 @@ public final class MacAddress implements Parcelable { /** * Returns a generated MAC address whose 24 least significant bits constituting the - * NIC part of the address are randomly selected. + * NIC part of the address are randomly selected and has Google OUI base. * * The locally assigned bit is always set to 1. The multicast bit is always set to 0. * - * @return a random locally assigned MacAddress. + * @return a random locally assigned, unicast MacAddress with Google OUI. + * + * @hide + */ + public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() { + return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom()); + } + + /** + * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the + * unicast bit, are randomly selected. + * + * The locally assigned bit is always set to 1. The multicast bit is always set to 0. + * + * @return a random locally assigned, unicast MacAddress. * * @hide */ public static @NonNull MacAddress createRandomUnicastAddress() { - return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random()); + SecureRandom r = new SecureRandom(); + long addr = r.nextLong() & VALID_LONG_MASK; + addr |= LOCALLY_ASSIGNED_MASK; + addr &= ~MULTICAST_MASK; + return new MacAddress(addr); } /** @@ -355,8 +374,8 @@ public final class MacAddress implements Parcelable { */ public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); - addr = addr | LOCALLY_ASSIGNED_MASK; - addr = addr & ~MULTICAST_MASK; + addr |= LOCALLY_ASSIGNED_MASK; + addr &= ~MULTICAST_MASK; return new MacAddress(addr); } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 4aadc5b49e71..682fdb7160f4 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -784,7 +784,7 @@ final class BinderProxy implements IBinder { private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE; private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1; // Debuggable builds will throw an AssertionError if the number of map entries exceeds: - private static final int CRASH_AT_SIZE = 5_000; + private static final int CRASH_AT_SIZE = 20_000; /** * We next warn when we exceed this bucket size. diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index a565dee5ddd0..fbdf27e38d67 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -21,12 +21,6 @@ import android.annotation.SystemApi; /** @hide */ @SystemApi public interface IHwBinder { - // These MUST match their corresponding libhwbinder/IBinder.h definition !!! - /** @hide */ - public static final int FIRST_CALL_TRANSACTION = 1; - /** @hide */ - public static final int FLAG_ONEWAY = 1; - /** * Process a hwbinder transaction. * diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 2093cec769da..7e7af1a12299 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -943,6 +943,20 @@ public class UserManager { * @see #getUserRestrictions() */ public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile"; + + /** + * Specifies whether the user is allowed to print. + * + * This restriction can be set by device or profile owner. + * + * The default value is {@code false}. + * + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_PRINTING = "no_printing"; + /** * Application restriction key that is used to indicate the pending arrival * of real restrictions for the app. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 567127a22fa7..84996e03a641 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7396,7 +7396,8 @@ public final class Settings { */ public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode"; - private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = BOOLEAN_VALIDATOR; + private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = + new SettingsValidators.InclusiveIntegerRangeValidator(0, 2); /** * Control the color temperature of Night Display, represented in Kelvin. @@ -8389,10 +8390,10 @@ public final class Settings { private static final Validator POWER_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; /** - * URI for the "wireless charging started" sound. + * URI for the "wireless charging started" and "wired charging started" sound. * @hide */ - public static final String WIRELESS_CHARGING_STARTED_SOUND = + public static final String CHARGING_STARTED_SOUND = "wireless_charging_started_sound"; /** @@ -10271,6 +10272,20 @@ public final class Settings { public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants"; /** + * An integer to show the version of the anomaly config. Ex: 1, which means + * current version is 1. + * @hide + */ + public static final String ANOMALY_CONFIG_VERSION = "anomaly_config_version"; + + /** + * A base64-encoded string represents anomaly stats config, used for + * {@link android.app.StatsManager}. + * @hide + */ + public static final String ANOMALY_CONFIG = "anomaly_config"; + + /** * Always on display(AOD) specific settings * This is encoded as a key=value list, separated by commas. Ex: * @@ -10301,16 +10316,6 @@ public final class Settings { public static final String SYS_VDSO = "sys_vdso"; /** - * Uid CpuPower global setting. This links the uid.cpupower system property. - * The following values are supported: - * 0 -> /proc/uid_cpupower/* are disabled - * 1 -> /proc/uid_cpupower/* are enabled - * Any other value defaults to enabled. - * @hide - */ - public static final String UID_CPUPOWER = "uid_cpupower"; - - /** * An integer to reduce the FPS by this factor. Only for experiments. Need to reboot the * device for this setting to take full effect. * @@ -10319,6 +10324,15 @@ public final class Settings { public static final String FPS_DEVISOR = "fps_divisor"; /** + * Flag to enable or disable display panel low power mode (lpm) + * false -> Display panel power saving mode is disabled. + * true -> Display panel power saving mode is enabled. + * + * @hide + */ + public static final String DISPLAY_PANEL_LPM = "display_panel_lpm"; + + /** * App standby (app idle) specific settings. * This is encoded as a key=value list, separated by commas. Ex: * <p> diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index 29c6cea14980..7383de738ad6 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -44,6 +44,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; import java.util.Map; /** @@ -247,4 +248,13 @@ public final class AutofillServiceInfo { && !mCompatibilityPackages.isEmpty()).append("]"); return builder.toString(); } + + /** + * Dumps it! + */ + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName()); + pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity); + pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages); + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 79fc13424bee..c460a3300536 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17,6 +17,7 @@ package android.view; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; + import static java.lang.Math.max; import android.animation.AnimatorInflater; @@ -8378,7 +8379,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityNodeProvider provider, AccessibilityNodeInfo info, boolean forAutofill) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), - null, null, null); + null, null, info.getViewIdResourceName()); Rect rect = structure.getTempRect(); info.getBoundsInParent(rect); structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height()); @@ -8418,6 +8419,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, CharSequence cname = info.getClassName(); structure.setClassName(cname != null ? cname.toString() : null); structure.setContentDescription(info.getContentDescription()); + if (forAutofill) { + final int maxTextLength = info.getMaxTextLength(); + if (maxTextLength != -1) { + structure.setMaxTextLength(maxTextLength); + } + structure.setHint(info.getHintText()); + } if ((info.getText() != null || info.getError() != null)) { structure.setText(info.getText(), info.getTextSelectionStart(), info.getTextSelectionEnd()); @@ -8428,7 +8436,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final AutofillValue autofillValue = AutofillValue.forText(structure.getText()); structure.setAutofillValue(autofillValue); if (info.isPassword()) { - structure.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); + structure.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); } } else { structure.setDataIsSensitive(false); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 273f097edc63..8e60a720f729 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3971,15 +3971,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Layout debugging code which draws rectangles around layout params. - * - * <p>This function is called automatically when the developer setting is enabled.<p/> - * - * <p>It is strongly advised to only call this function from debug builds as there is - * a risk of leaking unwanted layout information.<p/> - * - * @param canvas the canvas on which to draw - * @param paint the paint used to draw through + * @hide */ protected void onDebugDrawMargins(Canvas canvas, Paint paint) { for (int i = 0; i < getChildCount(); i++) { @@ -3989,19 +3981,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Layout debugging code which draws rectangles around: - * <ul> - * <li>optical bounds<li/> - * <li>margins<li/> - * <li>clip bounds<li/> - * <ul/> - * - * <p>This function is called automatically when the developer setting is enabled.<p/> - * - * <p>It is strongly advised to only call this function from debug builds as there is - * a risk of leaking unwanted layout information.<p/> - * - * @param canvas the canvas on which to draw + * @hide */ protected void onDebugDraw(Canvas canvas) { Paint paint = getDebugPaint(); @@ -7732,14 +7712,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters. * - * <p>This function is called automatically when the developer setting is enabled.<p/> - * - * <p>It is strongly advised to only call this function from debug builds as there is - * a risk of leaking unwanted layout information.<p/> - * * @param view the view that contains these layout parameters * @param canvas the canvas on which to draw - * @param paint the paint used to draw through + * + * @hide */ public void onDebugDraw(View view, Canvas canvas, Paint paint) { } @@ -8243,6 +8219,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL); } + /** + * @hide + */ @Override public void onDebugDraw(View view, Canvas canvas, Paint paint) { Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 63a9990cf839..5131a8ad4bf0 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -16,6 +16,10 @@ package android.view.autofill; +import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; +import static android.view.autofill.Helper.sDebug; +import static android.view.autofill.Helper.sVerbose; + import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.IntDef; import android.annotation.NonNull; @@ -47,13 +51,14 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; + import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; + import org.xmlpull.v1.XmlPullParserException; -import sun.misc.Cleaner; import java.io.IOException; import java.io.PrintWriter; @@ -66,9 +71,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; -import static android.view.autofill.Helper.sDebug; -import static android.view.autofill.Helper.sVerbose; +import sun.misc.Cleaner; // TODO: use java.lang.ref.Cleaner once Android supports Java 9 @@ -616,10 +619,8 @@ public final class AutofillManager { /** * @hide */ - public boolean isCompatibilityModeEnabled() { - synchronized (mLock) { - return mCompatibilityBridge != null; - } + public boolean isCompatibilityModeEnabledLocked() { + return mCompatibilityBridge != null; } /** @@ -1381,7 +1382,8 @@ public final class AutofillManager { @NonNull AutofillValue value, int flags) { if (sVerbose) { Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value - + ", flags=" + flags + ", state=" + getStateAsStringLocked()); + + ", flags=" + flags + ", state=" + getStateAsStringLocked() + + ", compatMode=" + isCompatibilityModeEnabledLocked()); } if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { if (sVerbose) { @@ -1392,7 +1394,7 @@ public final class AutofillManager { } try { final AutofillClient client = getClient(); - if (client == null) return; // NOTE: getClient() already logd it.. + if (client == null) return; // NOTE: getClient() already logged it.. mSessionId = mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), @@ -1939,7 +1941,8 @@ public final class AutofillManager { pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); - pw.print(pfx); pw.print("compat mode enabled: "); pw.println(isCompatibilityModeEnabled()); + pw.print(pfx); pw.print("compat mode enabled: "); pw.println( + isCompatibilityModeEnabledLocked()); pw.print(pfx); pw.print("debug: "); pw.print(sDebug); pw.print(" verbose: "); pw.println(sVerbose); } diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java index 8edf97ea0336..69c38ee4db4f 100644 --- a/core/java/android/view/textclassifier/SmartSelection.java +++ b/core/java/android/view/textclassifier/SmartSelection.java @@ -108,9 +108,9 @@ final class SmartSelection { } /** - * Returns the language of the model. + * Returns a comma separated list of locales supported by the model as BCP 47 tags. */ - public static String getLanguage(int fd) { + public static String getLanguages(int fd) { return nativeGetLanguage(fd); } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 9f389ba0c140..fc034937312c 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -27,8 +27,10 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Bundle; import android.os.LocaleList; import android.os.ParcelFileDescriptor; +import android.os.UserManager; import android.provider.Browser; import android.provider.CalendarContract; import android.provider.ContactsContract; @@ -56,6 +58,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.StringJoiner; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -99,11 +102,9 @@ public final class TextClassifierImpl implements TextClassifier { private final Object mLock = new Object(); @GuardedBy("mLock") // Do not access outside this lock. - private Map<Locale, String> mModelFilePaths; + private List<ModelFile> mAllModelFiles; @GuardedBy("mLock") // Do not access outside this lock. - private Locale mLocale; - @GuardedBy("mLock") // Do not access outside this lock. - private int mVersion; + private ModelFile mModel; @GuardedBy("mLock") // Do not access outside this lock. private SmartSelection mSmartSelection; @@ -279,18 +280,18 @@ public final class TextClassifierImpl implements TextClassifier { private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { synchronized (mLock) { localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; - final Locale locale = findBestSupportedLocaleLocked(localeList); - if (locale == null) { - throw new FileNotFoundException("No file for null locale"); + final ModelFile bestModel = findBestModelLocked(localeList); + if (bestModel == null) { + throw new FileNotFoundException("No model for " + localeList.toLanguageTags()); } - if (mSmartSelection == null || !Objects.equals(mLocale, locale)) { + if (mSmartSelection == null || !Objects.equals(mModel, bestModel)) { + Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel); destroySmartSelectionIfExistsLocked(); - final ParcelFileDescriptor fd = getFdLocked(locale); - final int modelFd = fd.getFd(); - mVersion = SmartSelection.getVersion(modelFd); - mSmartSelection = new SmartSelection(modelFd); + final ParcelFileDescriptor fd = ParcelFileDescriptor.open( + new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY); + mSmartSelection = new SmartSelection(fd.getFd()); closeAndLogError(fd); - mLocale = locale; + mModel = bestModel; } return mSmartSelection; } @@ -298,74 +299,8 @@ public final class TextClassifierImpl implements TextClassifier { private String getSignature(String text, int start, int end) { synchronized (mLock) { - return DefaultLogger.createSignature(text, start, end, mContext, mVersion, mLocale); - } - } - - @GuardedBy("mLock") // Do not call outside this lock. - private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException { - ParcelFileDescriptor updateFd; - int updateVersion = -1; - try { - updateFd = ParcelFileDescriptor.open( - new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY); - if (updateFd != null) { - updateVersion = SmartSelection.getVersion(updateFd.getFd()); - } - } catch (FileNotFoundException e) { - updateFd = null; - } - ParcelFileDescriptor factoryFd; - int factoryVersion = -1; - try { - final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale); - if (factoryModelFilePath != null) { - factoryFd = ParcelFileDescriptor.open( - new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY); - if (factoryFd != null) { - factoryVersion = SmartSelection.getVersion(factoryFd.getFd()); - } - } else { - factoryFd = null; - } - } catch (FileNotFoundException e) { - factoryFd = null; - } - - if (updateFd == null) { - if (factoryFd != null) { - return factoryFd; - } else { - throw new FileNotFoundException( - String.format(Locale.US, "No model file found for %s", locale)); - } - } - - final int updateFdInt = updateFd.getFd(); - final boolean localeMatches = Objects.equals( - locale.getLanguage().trim().toLowerCase(), - SmartSelection.getLanguage(updateFdInt).trim().toLowerCase()); - if (factoryFd == null) { - if (localeMatches) { - return updateFd; - } else { - closeAndLogError(updateFd); - throw new FileNotFoundException( - String.format(Locale.US, "No model file found for %s", locale)); - } - } - - if (!localeMatches) { - closeAndLogError(updateFd); - return factoryFd; - } - - if (updateVersion > factoryVersion) { - closeAndLogError(factoryFd); - return updateFd; - } else { - closeAndLogError(updateFd); - return factoryFd; + return DefaultLogger.createSignature(text, start, end, mContext, mModel.getVersion(), + mModel.getSupportedLocales()); } } @@ -377,60 +312,66 @@ public final class TextClassifierImpl implements TextClassifier { } } + /** + * Finds the most appropriate model to use for the given target locale list. + * + * The basic logic is: we ignore all models that don't support any of the target locales. For + * the remaining candidates, we take the update model unless its version number is lower than + * the factory version. It's assumed that factory models do not have overlapping locale ranges + * and conflict resolution between these models hence doesn't matter. + */ @GuardedBy("mLock") // Do not call outside this lock. @Nullable - private Locale findBestSupportedLocaleLocked(LocaleList localeList) { + private ModelFile findBestModelLocked(LocaleList localeList) { // Specified localeList takes priority over the system default, so it is listed first. final String languages = localeList.isEmpty() ? LocaleList.getDefault().toLanguageTags() : localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags(); final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages); - final List<Locale> supportedLocales = - new ArrayList<>(getFactoryModelFilePathsLocked().keySet()); - final Locale updatedModelLocale = getUpdatedModelLocale(); - if (updatedModelLocale != null) { - supportedLocales.add(updatedModelLocale); + ModelFile bestModel = null; + int bestModelVersion = -1; + for (ModelFile model : listAllModelsLocked()) { + if (model.isAnyLanguageSupported(languageRangeList)) { + if (model.getVersion() >= bestModelVersion) { + bestModel = model; + bestModelVersion = model.getVersion(); + } + } } - return Locale.lookup(languageRangeList, supportedLocales); + return bestModel; } + /** Returns a list of all model files available, in order of precedence. */ @GuardedBy("mLock") // Do not call outside this lock. - private Map<Locale, String> getFactoryModelFilePathsLocked() { - if (mModelFilePaths == null) { - final Map<Locale, String> modelFilePaths = new HashMap<>(); + private List<ModelFile> listAllModelsLocked() { + if (mAllModelFiles == null) { + final List<ModelFile> allModels = new ArrayList<>(); + // The update model has the highest precedence. + if (new File(UPDATED_MODEL_FILE_PATH).exists()) { + final ModelFile updatedModel = ModelFile.fromPath(UPDATED_MODEL_FILE_PATH); + if (updatedModel != null) { + allModels.add(updatedModel); + } + } + // Factory models should never have overlapping locales, so the order doesn't matter. final File modelsDir = new File(MODEL_DIR); if (modelsDir.exists() && modelsDir.isDirectory()) { - final File[] models = modelsDir.listFiles(); + final File[] modelFiles = modelsDir.listFiles(); final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX); - final int size = models.length; - for (int i = 0; i < size; i++) { - final File modelFile = models[i]; + for (File modelFile : modelFiles) { final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName()); if (matcher.matches() && modelFile.isFile()) { - final String language = matcher.group(1); - final Locale locale = Locale.forLanguageTag(language); - modelFilePaths.put(locale, modelFile.getAbsolutePath()); + final ModelFile model = ModelFile.fromPath(modelFile.getAbsolutePath()); + if (model != null) { + allModels.add(model); + } } } } - mModelFilePaths = modelFilePaths; - } - return mModelFilePaths; - } - - @Nullable - private Locale getUpdatedModelLocale() { - try { - final ParcelFileDescriptor updateFd = ParcelFileDescriptor.open( - new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY); - final Locale locale = Locale.forLanguageTag( - SmartSelection.getLanguage(updateFd.getFd())); - closeAndLogError(updateFd); - return locale; - } catch (FileNotFoundException e) { - return null; + mAllModelFiles = allModels; } + return mAllModelFiles; } private TextClassification createClassificationResult( @@ -520,6 +461,95 @@ public final class TextClassifierImpl implements TextClassifier { } /** + * Describes TextClassifier model files on disk. + */ + private static final class ModelFile { + + private final String mPath; + private final String mName; + private final int mVersion; + private final List<Locale> mSupportedLocales; + + /** Returns null if the path did not point to a compatible model. */ + static @Nullable ModelFile fromPath(String path) { + final File file = new File(path); + try { + final ParcelFileDescriptor modelFd = ParcelFileDescriptor.open( + file, ParcelFileDescriptor.MODE_READ_ONLY); + final int version = SmartSelection.getVersion(modelFd.getFd()); + final String supportedLocalesStr = SmartSelection.getLanguages(modelFd.getFd()); + if (supportedLocalesStr.isEmpty()) { + Log.d(DEFAULT_LOG_TAG, "Ignoring " + file.getAbsolutePath()); + return null; + } + final List<Locale> supportedLocales = new ArrayList<>(); + for (String langTag : supportedLocalesStr.split(",")) { + supportedLocales.add(Locale.forLanguageTag(langTag)); + } + closeAndLogError(modelFd); + return new ModelFile(path, file.getName(), version, supportedLocales); + } catch (FileNotFoundException e) { + Log.e(DEFAULT_LOG_TAG, "Failed to peek " + file.getAbsolutePath(), e); + return null; + } + } + + /** The absolute path to the model file. */ + String getPath() { + return mPath; + } + + /** A name to use for signature generation. Effectively the name of the model file. */ + String getName() { + return mName; + } + + /** Returns the version tag in the model's metadata. */ + int getVersion() { + return mVersion; + } + + /** Returns whether the language supports any language in the given ranges. */ + boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) { + return Locale.lookup(languageRanges, mSupportedLocales) != null; + } + + /** All locales supported by the model. */ + List<Locale> getSupportedLocales() { + return Collections.unmodifiableList(mSupportedLocales); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other == null || !ModelFile.class.isAssignableFrom(other.getClass())) { + return false; + } else { + final ModelFile otherModel = (ModelFile) other; + return mPath.equals(otherModel.mPath); + } + } + + @Override + public String toString() { + final StringJoiner localesJoiner = new StringJoiner(","); + for (Locale locale : mSupportedLocales) { + localesJoiner.add(locale.toLanguageTag()); + } + return String.format(Locale.US, "ModelFile { path=%s name=%s version=%d locales=%s }", + mPath, mName, mVersion, localesJoiner.toString()); + } + + private ModelFile(String path, String name, int version, List<Locale> supportedLocales) { + mPath = path; + mName = name; + mVersion = version; + mSupportedLocales = supportedLocales; + } + } + + /** * Creates intents based on the classification type. */ static final class IntentFactory { @@ -541,7 +571,7 @@ public final class TextClassifierImpl implements TextClassifier { case TextClassifier.TYPE_EMAIL: return createForEmail(text); case TextClassifier.TYPE_PHONE: - return createForPhone(text); + return createForPhone(context, text); case TextClassifier.TYPE_ADDRESS: return createForAddress(text); case TextClassifier.TYPE_URL: @@ -573,15 +603,23 @@ public final class TextClassifierImpl implements TextClassifier { } @NonNull - private static List<Intent> createForPhone(String text) { - return Arrays.asList( - new Intent(Intent.ACTION_DIAL) - .setData(Uri.parse(String.format("tel:%s", text))), - new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.PHONE, text), - new Intent(Intent.ACTION_SENDTO) - .setData(Uri.parse(String.format("smsto:%s", text)))); + private static List<Intent> createForPhone(Context context, String text) { + final List<Intent> intents = new ArrayList<>(); + final UserManager userManager = context.getSystemService(UserManager.class); + final Bundle userRestrictions = userManager != null + ? userManager.getUserRestrictions() : new Bundle(); + if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) { + intents.add(new Intent(Intent.ACTION_DIAL) + .setData(Uri.parse(String.format("tel:%s", text)))); + } + intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT) + .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) + .putExtra(ContactsContract.Intents.Insert.PHONE, text)); + if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) { + intents.add(new Intent(Intent.ACTION_SENDTO) + .setData(Uri.parse(String.format("smsto:%s", text)))); + } + return intents; } @NonNull diff --git a/core/java/android/view/textclassifier/logging/DefaultLogger.java b/core/java/android/view/textclassifier/logging/DefaultLogger.java index 6b848351cbf6..03a6d3a7f10f 100644 --- a/core/java/android/view/textclassifier/logging/DefaultLogger.java +++ b/core/java/android/view/textclassifier/logging/DefaultLogger.java @@ -17,7 +17,6 @@ package android.view.textclassifier.logging; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.metrics.LogMaker; import android.util.Log; @@ -27,8 +26,10 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.Preconditions; +import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.StringJoiner; /** * Default Logger. @@ -210,12 +211,16 @@ public final class DefaultLogger extends Logger { */ public static String createSignature( String text, int start, int end, Context context, int modelVersion, - @Nullable Locale locale) { + List<Locale> locales) { Preconditions.checkNotNull(text); Preconditions.checkNotNull(context); - final String modelName = (locale != null) - ? String.format(Locale.US, "%s_v%d", locale.toLanguageTag(), modelVersion) - : ""; + Preconditions.checkNotNull(locales); + final StringJoiner localesJoiner = new StringJoiner(","); + for (Locale locale : locales) { + localesJoiner.add(locale.toLanguageTag()); + } + final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(), + modelVersion); final int hash = Objects.hash(text, start, end, context.getPackageName()); return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash); } @@ -242,9 +247,9 @@ public final class DefaultLogger extends Logger { static String getModelName(String signature) { Preconditions.checkNotNull(signature); - final int start = signature.indexOf("|"); + final int start = signature.indexOf("|") + 1; final int end = signature.indexOf("|", start); - if (start >= 0 && end >= start) { + if (start >= 1 && end >= start) { return signature.substring(start, end); } return ""; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 5ab579df5ed6..41ceb306d744 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1299,6 +1299,16 @@ public class Editor { if (mSelectionModifierCursorController != null) { mSelectionModifierCursorController.resetTouchOffsets(); } + + ensureNoSelectionIfNonSelectable(); + } + } + + private void ensureNoSelectionIfNonSelectable() { + // This could be the case if a TextLink has been tapped. + if (!mTextView.textCanBeSelected() && mTextView.hasSelection()) { + Selection.setSelection((Spannable) mTextView.getText(), + mTextView.length(), mTextView.length()); } } @@ -1382,6 +1392,8 @@ public class Editor { // Don't leave us in the middle of a batch edit. Same as in onFocusChanged ensureEndedBatchEdit(); + + ensureNoSelectionIfNonSelectable(); } } diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 012b918ff34e..3aae8497ba1b 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -904,6 +904,9 @@ public class GridLayout extends ViewGroup { } } + /** + * @hide + */ @Override protected void onDebugDrawMargins(Canvas canvas, Paint paint) { // Apply defaults, so as to remove UNDEFINED values @@ -919,6 +922,9 @@ public class GridLayout extends ViewGroup { } } + /** + * @hide + */ @Override protected void onDebugDraw(Canvas canvas) { Paint paint = new Paint(); diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 7a4c800ba15e..88365617cd6e 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -18,8 +18,10 @@ package android.widget; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.annotation.TestApi; import android.annotation.UiThread; import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; @@ -269,4 +271,46 @@ public final class Magnifier { return mWindow.getContentView().findViewById( com.android.internal.R.id.magnifier_image); } + + /** + * @return the content being currently displayed in the magnifier, as bitmap + * + * @hide + */ + @TestApi + public Bitmap getContent() { + return mBitmap; + } + + /** + * @return the position of the magnifier window relative to the screen + * + * @hide + */ + @TestApi + public Rect getWindowPositionOnScreen() { + final int[] viewLocationOnScreen = new int[2]; + mView.getLocationOnScreen(viewLocationOnScreen); + final int[] viewLocationInSurface = new int[2]; + mView.getLocationInSurface(viewLocationInSurface); + + final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0]; + final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1]; + return new Rect(left, top, left + mWindow.getWidth(), top + mWindow.getHeight()); + } + + /** + * @return the size of the magnifier window in dp + * + * @hide + */ + @TestApi + public static PointF getMagnifierDefaultSize() { + final Resources resources = Resources.getSystem(); + final float density = resources.getDisplayMetrics().density; + final PointF size = new PointF(); + size.x = resources.getDimension(com.android.internal.R.dimen.magnifier_width) / density; + size.y = resources.getDimension(com.android.internal.R.dimen.magnifier_height) / density; + return size; + } } diff --git a/core/java/android/widget/TextInputTimePickerView.java b/core/java/android/widget/TextInputTimePickerView.java index 0cf8faad1c57..e0261ad04f58 100644 --- a/core/java/android/widget/TextInputTimePickerView.java +++ b/core/java/android/widget/TextInputTimePickerView.java @@ -174,7 +174,8 @@ public class TextInputTimePickerView extends RelativeLayout { */ void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour, boolean hourFormatStartsAtZero) { - final String format = "%d"; + final String hourFormat = "%d"; + final String minuteFormat = "%02d"; mIs24Hour = is24Hour; mHourFormatStartsAtZero = hourFormatStartsAtZero; @@ -187,8 +188,8 @@ public class TextInputTimePickerView extends RelativeLayout { mAmPmSpinner.setSelection(1); } - mHourEditText.setText(String.format(format, localizedHour)); - mMinuteEditText.setText(String.format(format, minute)); + mHourEditText.setText(String.format(hourFormat, localizedHour)); + mMinuteEditText.setText(String.format(minuteFormat, minute)); if (mErrorShowing) { validateInput(); diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index b22ce5e2a6ee..f6a69d9aeb93 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -16,157 +16,214 @@ package com.android.internal.app; -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.annotation.Nullable; +import android.animation.TimeAnimator; import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ContentResolver; -import android.content.Intent; -import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.RippleDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; import android.os.Bundle; -import android.provider.Settings; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.MathUtils; -import android.view.Gravity; -import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.animation.PathInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; public class PlatLogoActivity extends Activity { - public static final boolean FINISH = true; + FrameLayout layout; + TimeAnimator anim; + PBackground bg; - FrameLayout mLayout; - int mTapCount; - int mKeyCount; - PathInterpolator mInterpolator = new PathInterpolator(0f, 0f, 0.5f, 1f); + private class PBackground extends Drawable { + private float maxRadius, radius, x, y, dp; + private int[] palette; + private int darkest; + private float offset; + + public PBackground() { + randomizePalette(); + } + + /** + * set inner radius of "p" logo + */ + public void setRadius(float r) { + this.radius = Math.max(48*dp, r); + } + + /** + * move the "p" + */ + public void setPosition(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * for animating the "p" + */ + public void setOffset(float o) { + this.offset = o; + } + + /** + * rough luminance calculation + * https://www.w3.org/TR/AERT/#color-contrast + */ + public float lum(int rgb) { + return ((Color.red(rgb) * 299f) + (Color.green(rgb) * 587f) + (Color.blue(rgb) * 114f)) / 1000f; + } + + /** + * create a random evenly-spaced color palette + * guaranteed to contrast! + */ + public void randomizePalette() { + final int slots = 2 + (int)(Math.random() * 2); + float[] color = new float[] { (float) Math.random() * 360f, 1f, 1f }; + palette = new int[slots]; + darkest = 0; + for (int i=0; i<slots; i++) { + palette[i] = Color.HSVToColor(color); + color[0] += 360f/slots; + if (lum(palette[i]) < lum(palette[darkest])) darkest = i; + } + + final StringBuilder str = new StringBuilder(); + for (int c : palette) { + str.append(String.format("#%08x ", c)); + } + Log.v("PlatLogoActivity", "color palette: " + str); + } + + @Override + public void draw(Canvas canvas) { + if (dp == 0) dp = getResources().getDisplayMetrics().density; + final float width = canvas.getWidth(); + final float height = canvas.getHeight(); + if (radius == 0) { + setPosition(width / 2, height / 2); + setRadius(width / 6); + } + final float inner_w = radius * 0.667f; + + final Paint paint = new Paint(); + paint.setStrokeCap(Paint.Cap.BUTT); + canvas.translate(x, y); + + Path p = new Path(); + p.moveTo(-radius, height); + p.lineTo(-radius, 0); + p.arcTo(-radius, -radius, radius, radius, -180, 270, false); + p.lineTo(-radius, radius); + + float w = Math.max(canvas.getWidth(), canvas.getHeight()) * 1.414f; + paint.setStyle(Paint.Style.FILL); + + int i=0; + while (w > radius*2 + inner_w*2) { + paint.setColor(0xFF000000 | palette[i % palette.length]); + // for a slower but more complete version: + // paint.setStrokeWidth(w); + // canvas.drawPath(p, paint); + canvas.drawOval(-w/2, -w/2, w/2, w/2, paint); + w -= inner_w * (1.1f + Math.sin((i/20f + offset) * 3.14159f)); + i++; + } + + // the innermost circle needs to be a constant color to avoid rapid flashing + paint.setColor(0xFF000000 | palette[(darkest+1) % palette.length]); + canvas.drawOval(-radius, -radius, radius, radius, paint); + + p.reset(); + p.moveTo(-radius, height); + p.lineTo(-radius, 0); + p.arcTo(-radius, -radius, radius, radius, -180, 270, false); + p.lineTo(-radius + inner_w, radius); + + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(inner_w*2); + paint.setColor(palette[darkest]); + canvas.drawPath(p, paint); + paint.setStrokeWidth(inner_w); + paint.setColor(0xFFFFFFFF); + canvas.drawPath(p, paint); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return 0; + } + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mLayout = new FrameLayout(this); - setContentView(mLayout); - } + layout = new FrameLayout(this); + setContentView(layout); - @Override - public void onAttachedToWindow() { - final DisplayMetrics dm = getResources().getDisplayMetrics(); - final float dp = dm.density; - final int size = (int) - (Math.min(Math.min(dm.widthPixels, dm.heightPixels), 600*dp) - 100*dp); - - final ImageView im = new ImageView(this); - final int pad = (int)(40*dp); - im.setPadding(pad, pad, pad, pad); - im.setTranslationZ(20); - im.setScaleX(0.5f); - im.setScaleY(0.5f); - im.setAlpha(0f); - - im.setBackground(new RippleDrawable( - ColorStateList.valueOf(0xFF776677), - getDrawable(com.android.internal.R.drawable.platlogo), - null)); - im.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - final int w = view.getWidth(); - final int h = view.getHeight(); - outline.setOval((int)(w*.125), (int)(h*.125), (int)(w*.96), (int)(h*.96)); - } - }); - im.setElevation(12f*dp); - im.setClickable(true); - im.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - im.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mTapCount < 5) return false; - - final ContentResolver cr = getContentResolver(); - if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) - == 0) { - // For posterity: the moment this user unlocked the easter egg - try { - Settings.System.putLong(cr, - Settings.System.EGG_MODE, - System.currentTimeMillis()); - } catch (RuntimeException e) { - Log.e("PlatLogoActivity", "Can't write settings", e); - } - } - im.post(new Runnable() { - @Override - public void run() { - try { - startActivity(new Intent(Intent.ACTION_MAIN) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .addCategory("com.android.internal.category.PLATLOGO")); - } catch (ActivityNotFoundException ex) { - Log.e("PlatLogoActivity", "No more eggs."); - } - if (FINISH) finish(); - } - }); - return true; - } - }); - mTapCount++; - } - }); + bg = new PBackground(); + layout.setBackground(bg); + + layout.setOnTouchListener(new View.OnTouchListener() { + final PointerCoords pc0 = new PointerCoords(); + final PointerCoords pc1 = new PointerCoords(); - // Enable hardware keyboard input for TV compatibility. - im.setFocusable(true); - im.requestFocus(); - im.setOnKeyListener(new View.OnKeyListener() { @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode != KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { - ++mKeyCount; - if (mKeyCount > 2) { - if (mTapCount > 5) { - im.performLongClick(); - } else { - im.performClick(); + public boolean onTouch(View v, MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + if (event.getPointerCount() > 1) { + event.getPointerCoords(0, pc0); + event.getPointerCoords(1, pc1); + bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f); } - } - return true; - } else { - return false; + break; } + return true; } }); + } + + @Override + public void onStart() { + super.onStart(); + + bg.randomizePalette(); - mLayout.addView(im, new FrameLayout.LayoutParams(size, size, Gravity.CENTER)); + anim = new TimeAnimator(); + anim.setTimeListener( + new TimeAnimator.TimeListener() { + @Override + public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { + bg.setOffset((float) totalTime / 60000f); + bg.invalidateSelf(); + } + }); - im.animate().scaleX(1f).scaleY(1f).alpha(1f) - .setInterpolator(mInterpolator) - .setDuration(500) - .setStartDelay(800) - .start(); + anim.start(); + } + + @Override + public void onStop() { + if (anim != null) { + anim.cancel(); + anim = null; + } + super.onStop(); } } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 1dfff5efcab5..6bd693061a85 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -106,7 +106,7 @@ public class ResolverListController { int flags = PackageManager.MATCH_DEFAULT_ONLY | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); - if (intent.isBrowsableWebIntent() + if (intent.isWebIntent() || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { flags |= PackageManager.MATCH_INSTANT; } diff --git a/core/java/com/android/internal/colorextraction/types/Tonal.java b/core/java/com/android/internal/colorextraction/types/Tonal.java index 9b7383fcbe8a..7b25a0691c40 100644 --- a/core/java/com/android/internal/colorextraction/types/Tonal.java +++ b/core/java/com/android/internal/colorextraction/types/Tonal.java @@ -405,12 +405,13 @@ public class Tonal implements ExtractionType { return v - (float) Math.floor(v); } - static class TonalPalette { - final float[] h; - final float[] s; - final float[] l; - final float minHue; - final float maxHue; + @VisibleForTesting + public static class TonalPalette { + public final float[] h; + public final float[] s; + public final float[] l; + public final float minHue; + public final float maxHue; TonalPalette(float[] h, float[] s, float[] l) { if (h.length != s.length || s.length != l.length) { diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 40dcf25bbd10..ecab15fb81a5 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -3522,7 +3522,7 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; } - void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { + void addHistoryBufferLocked(long elapsedRealtimeMs, HistoryItem cur) { if (!mHaveBatteryLevel || !mRecordingHistory) { return; } @@ -3603,8 +3603,8 @@ public class BatteryStatsImpl extends BatteryStats { } else if (dataSize >= MAX_HISTORY_BUFFER) { if (!mHistoryOverflow) { mHistoryOverflow = true; - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur); return; } @@ -3642,7 +3642,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); return; } @@ -3650,15 +3650,14 @@ public class BatteryStatsImpl extends BatteryStats { // The history is currently empty; we need it to start with a time stamp. cur.currentTime = System.currentTimeMillis(); if (recordResetDueToOverflow) { - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur); } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur); } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); } - private void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, - HistoryItem cur) { + private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) { if (mIteratingHistory) { throw new IllegalStateException("Can't do this while iterating history!"); } @@ -3692,17 +3691,17 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryAddTmp.wakeReasonTag = null; mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE; mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG; - addHistoryRecordInnerLocked(wakeElapsedTime, uptimeMs, mHistoryAddTmp); + addHistoryRecordInnerLocked(wakeElapsedTime, mHistoryAddTmp); } } mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG; mTrackRunningHistoryElapsedRealtime = elapsedRealtimeMs; mTrackRunningHistoryUptime = uptimeMs; - addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur); + addHistoryRecordInnerLocked(elapsedRealtimeMs, mHistoryCur); } - void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur); + void addHistoryRecordInnerLocked(long elapsedRealtimeMs, HistoryItem cur) { + addHistoryBufferLocked(elapsedRealtimeMs, cur); if (!USE_OLD_HISTORY) { return; @@ -3743,7 +3742,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mNumHistoryItems == MAX_HISTORY_ITEMS || mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) { - addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur); } if (mNumHistoryItems >= MAX_HISTORY_ITEMS) { @@ -3760,7 +3759,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); } public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, @@ -9931,8 +9930,6 @@ public class BatteryStatsImpl extends BatteryStats { if (wl != null) { StopwatchTimer wlt = getWakelockTimerLocked(wl, type); wlt.stopRunningLocked(elapsedRealtimeMs); - if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped - } } if (type == WAKE_TYPE_PARTIAL) { if (mAggregatedPartialWakelockTimer != null) { @@ -12307,7 +12304,7 @@ public class BatteryStatsImpl extends BatteryStats { boolean reset) { mRecordingHistory = true; mHistoryCur.currentTime = System.currentTimeMillis(); - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, + addHistoryBufferLocked(elapsedRealtimeMs, reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME, mHistoryCur); mHistoryCur.currentTime = 0; @@ -12320,8 +12317,7 @@ public class BatteryStatsImpl extends BatteryStats { final long uptimeMs) { if (mRecordingHistory) { mHistoryCur.currentTime = currentTime; - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME, - mHistoryCur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur); mHistoryCur.currentTime = 0; } } @@ -12329,8 +12325,7 @@ public class BatteryStatsImpl extends BatteryStats { private void recordShutdownLocked(final long elapsedRealtimeMs, final long uptimeMs) { if (mRecordingHistory) { mHistoryCur.currentTime = System.currentTimeMillis(); - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_SHUTDOWN, - mHistoryCur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur); mHistoryCur.currentTime = 0; } } @@ -13274,7 +13269,7 @@ public class BatteryStatsImpl extends BatteryStats { if (USE_OLD_HISTORY) { addHistoryRecordLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur); } - addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur); + addHistoryBufferLocked(elapsedRealtime, HistoryItem.CMD_START, mHistoryCur); startRecordingHistory(elapsedRealtime, uptime, false); } diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index 74dbaba32043..f05bd32ba2cd 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -139,6 +139,10 @@ public final class InputBindResult implements Parcelable { * @see com.android.server.wm.WindowManagerService#inputMethodClientHasFocus(IInputMethodClient) */ int ERROR_NOT_IME_TARGET_WINDOW = 11; + /** + * Indicates that focused view in the current window is not an editor. + */ + int ERROR_NO_EDITOR = 12; } @ResultCode @@ -258,6 +262,8 @@ public final class InputBindResult implements Parcelable { return "ERROR_NULL"; case ResultCode.ERROR_NO_IME: return "ERROR_NO_IME"; + case ResultCode.ERROR_NO_EDITOR: + return "ERROR_NO_EDITOR"; case ResultCode.ERROR_INVALID_PACKAGE_NAME: return "ERROR_INVALID_PACKAGE_NAME"; case ResultCode.ERROR_SYSTEM_NOT_READY: @@ -288,6 +294,10 @@ public final class InputBindResult implements Parcelable { */ public static final InputBindResult NO_IME = error(ResultCode.ERROR_NO_IME); /** + * Predefined error object for {@link ResultCode#NO_EDITOR}. + */ + public static final InputBindResult NO_EDITOR = error(ResultCode.ERROR_NO_EDITOR); + /** * Predefined error object for {@link ResultCode#ERROR_INVALID_PACKAGE_NAME}. */ public static final InputBindResult INVALID_PACKAGE_NAME = diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java new file mode 100644 index 000000000000..71d3bb5d6b5c --- /dev/null +++ b/core/java/com/android/internal/widget/LocalImageResolver.java @@ -0,0 +1,76 @@ +/* + * 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.internal.widget; + +import android.annotation.Nullable; +import android.app.Notification; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A class to extract Bitmaps from a MessagingStyle message. + */ +public class LocalImageResolver { + + private static final int MAX_SAFE_ICON_SIZE_PX = 480; + + @Nullable + public static Drawable resolveImage(Uri uri, Context context) throws IOException { + BitmapFactory.Options onlyBoundsOptions = getBoundsOptionsForImage(uri, context); + if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) { + return null; + } + + int originalSize = + (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) + ? onlyBoundsOptions.outHeight + : onlyBoundsOptions.outWidth; + + double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX) + ? (originalSize / MAX_SAFE_ICON_SIZE_PX) + : 1.0; + + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio); + InputStream input = context.getContentResolver().openInputStream(uri); + Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); + input.close(); + return new BitmapDrawable(context.getResources(), bitmap); + } + + private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context) + throws IOException { + InputStream input = context.getContentResolver().openInputStream(uri); + BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); + onlyBoundsOptions.inJustDecodeBounds = true; + BitmapFactory.decodeStream(input, null, onlyBoundsOptions); + input.close(); + return onlyBoundsOptions; + } + + private static int getPowerOfTwoForSampleRatio(double ratio) { + int k = Integer.highestOneBit((int) Math.floor(ratio)); + return Math.max(1, k); + } +} diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 5577d6e86fee..239beaa220e0 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -22,9 +22,12 @@ import android.annotation.Nullable; import android.annotation.StyleRes; import android.app.Notification; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.Icon; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Pools; import android.view.LayoutInflater; import android.view.View; @@ -61,6 +64,11 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private boolean mIsHidingAnimated; private boolean mNeedsGeneratedAvatar; private Notification.Person mSender; + private boolean mAvatarsAtEnd; + private ViewGroup mImageContainer; + private MessagingImageMessage mIsolatedMessage; + private boolean mTransformingImages; + private Point mDisplaySize = new Point(); public MessagingGroup(@NonNull Context context) { super(context); @@ -87,6 +95,35 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou mSenderName = findViewById(R.id.message_name); mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); mAvatarView = findViewById(R.id.message_icon); + mImageContainer = findViewById(R.id.messaging_group_icon_container); + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + mDisplaySize.x = displayMetrics.widthPixels; + mDisplaySize.y = displayMetrics.heightPixels; + } + + public void updateClipRect() { + // We want to clip to the senderName if it's available, otherwise our images will come + // from a weird position + Rect clipRect; + if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) { + ViewGroup parent = (ViewGroup) mSenderName.getParent(); + int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent( + mMessageContainer, parent) + mSenderName.getHeight(); + clipRect = new Rect(0, top, mDisplaySize.x, mDisplaySize.y); + } else { + clipRect = null; + } + mMessageContainer.setClipBounds(clipRect); + } + + private int getDistanceFromParent(View searchedView, ViewGroup parent) { + int position = 0; + View view = searchedView; + while(view != parent) { + position += view.getTop() + view.getTranslationY(); + view = (View) view.getParent(); + } + return position; } public void setSender(Notification.Person sender, CharSequence nameOverride) { @@ -129,12 +166,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } public void removeMessage(MessagingMessage messagingMessage) { - mMessageContainer.removeView(messagingMessage); + ViewGroup messageParent = (ViewGroup) messagingMessage.getView().getParent(); + messageParent.removeView(messagingMessage.getView()); Runnable recycleRunnable = () -> { - mMessageContainer.removeTransientView(messagingMessage); + messageParent.removeTransientView(messagingMessage.getView()); messagingMessage.recycle(); if (mMessageContainer.getChildCount() == 0 - && mMessageContainer.getTransientViewCount() == 0) { + && mMessageContainer.getTransientViewCount() == 0 + && mImageContainer.getChildCount() == 0) { ViewParent parent = getParent(); if (parent instanceof ViewGroup) { ((ViewGroup) parent).removeView(MessagingGroup.this); @@ -148,9 +187,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } }; if (isShown()) { - mMessageContainer.addTransientView(messagingMessage, 0); - performRemoveAnimation(messagingMessage, recycleRunnable); - if (mMessageContainer.getChildCount() == 0) { + messageParent.addTransientView(messagingMessage.getView(), 0); + performRemoveAnimation(messagingMessage.getView(), recycleRunnable); + if (mMessageContainer.getChildCount() == 0 + && mImageContainer.getChildCount() == 0) { removeGroupAnimated(null); } } else { @@ -160,12 +200,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } private void removeGroupAnimated(Runnable endAction) { - MessagingPropertyAnimator.fadeOut(mAvatarView, null); - MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView, - (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN); - MessagingPropertyAnimator.fadeOut(mSenderName, null); - MessagingPropertyAnimator.startLocalTranslationTo(mSenderName, - (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN); + performRemoveAnimation(mAvatarView, null); + performRemoveAnimation(mSenderName, null); boolean endActionTriggered = false; for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) { View child = mMessageContainer.getChildAt(i); @@ -182,14 +218,17 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou performRemoveAnimation(child, childEndAction); endActionTriggered = true; } + if (mIsolatedMessage != null) { + performRemoveAnimation(mIsolatedMessage, !endActionTriggered ? endAction : null); + endActionTriggered = true; + } if (!endActionTriggered && endAction != null) { endAction.run(); } } - public void performRemoveAnimation(View message, - Runnable recycleRunnable) { - MessagingPropertyAnimator.fadeOut(message, recycleRunnable); + public void performRemoveAnimation(View message, Runnable endAction) { + MessagingPropertyAnimator.fadeOut(message, endAction); MessagingPropertyAnimator.startLocalTranslationTo(message, (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN); } @@ -222,6 +261,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } } } + if (mMessageContainer.getChildCount() == 0 && mIsolatedMessage != null) { + return mIsolatedMessage.getMeasuredType(); + } return MEASURED_NORMAL; } @@ -234,6 +276,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines(); } } + result = mIsolatedMessage != null ? Math.max(result, 1) : result; // A group is usually taking up quite some space with the padding and the name, let's add 1 return result + 1; } @@ -289,26 +332,67 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setMessages(List<MessagingMessage> group) { // Let's now make sure all children are added and in the correct order + int textMessageIndex = 0; + MessagingImageMessage isolatedMessage = null; for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) { MessagingMessage message = group.get(messageIndex); + message.setColor(mTextColor); if (message.getGroup() != this) { message.setMessagingGroup(this); - ViewParent parent = mMessageContainer.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(message); - } - mMessageContainer.addView(message, messageIndex); mAddedMessages.add(message); } - if (messageIndex != mMessageContainer.indexOfChild(message)) { - mMessageContainer.removeView(message); - mMessageContainer.addView(message, messageIndex); + boolean isImage = message instanceof MessagingImageMessage; + if (mAvatarsAtEnd && isImage) { + isolatedMessage = (MessagingImageMessage) message; + } else { + if (removeFromParentIfDifferent(message, mMessageContainer)) { + ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams(); + if (layoutParams != null + && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) { + message.getView().setLayoutParams( + mMessageContainer.generateDefaultLayoutParams()); + } + mMessageContainer.addView(message.getView(), textMessageIndex); + } + if (isImage) { + ((MessagingImageMessage) message).setIsolated(false); + } + // Let's sort them properly + if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) { + mMessageContainer.removeView(message.getView()); + mMessageContainer.addView(message.getView(), textMessageIndex); + } + textMessageIndex++; + } + } + if (isolatedMessage != null) { + if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) { + mImageContainer.removeAllViews(); + mImageContainer.addView(isolatedMessage.getView()); } - message.setTextColor(mTextColor); + isolatedMessage.setIsolated(true); + } else if (mIsolatedMessage != null) { + mImageContainer.removeAllViews(); } + mIsolatedMessage = isolatedMessage; mMessages = group; } + /** + * Remove the message from the parent if the parent isn't the one provided + * @return whether the message was removed + */ + private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) { + ViewParent parent = message.getView().getParent(); + if (parent != newParent) { + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(message.getView()); + } + return true; + } + return false; + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -317,13 +401,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou @Override public boolean onPreDraw() { for (MessagingMessage message : mAddedMessages) { - if (!message.isShown()) { + if (!message.getView().isShown()) { continue; } - MessagingPropertyAnimator.fadeIn(message); + MessagingPropertyAnimator.fadeIn(message.getView()); if (!mFirstLayout) { - MessagingPropertyAnimator.startLocalTranslationFrom(message, - message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN); + MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(), + message.getView().getHeight(), + MessagingLayout.LINEAR_OUT_SLOW_IN); } } mAddedMessages.clear(); @@ -333,6 +418,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou }); } mFirstLayout = false; + updateClipRect(); } /** @@ -372,6 +458,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return mMessageContainer; } + public MessagingImageMessage getIsolatedMessage() { + return mIsolatedMessage; + } + public boolean needsGeneratedAvatar() { return mNeedsGeneratedAvatar; } @@ -379,4 +469,19 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public Notification.Person getSender() { return mSender; } + + public void setTransformingImages(boolean transformingImages) { + mTransformingImages = transformingImages; + } + + public void setDisplayAvatarsAtEnd(boolean atEnd) { + if (mAvatarsAtEnd != atEnd) { + mAvatarsAtEnd = atEnd; + mImageContainer.setVisibility(atEnd ? View.VISIBLE : View.GONE); + } + } + + public List<MessagingMessage> getMessages() { + return mMessages; + } } diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java new file mode 100644 index 000000000000..961f90a3c110 --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingImageMessage.java @@ -0,0 +1,273 @@ +/* + * 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.internal.widget; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StyleRes; +import android.app.Notification; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Pools; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RemoteViews; + +import com.android.internal.R; + +import java.io.IOException; + +/** + * A message of a {@link MessagingLayout} that is an image. + */ +@RemoteViews.RemoteView +public class MessagingImageMessage extends ImageView implements MessagingMessage { + private static final String TAG = "MessagingImageMessage"; + private static Pools.SimplePool<MessagingImageMessage> sInstancePool + = new Pools.SynchronizedPool<>(10); + private final MessagingMessageState mState = new MessagingMessageState(this); + private final int mMinImageHeight; + private final Path mPath = new Path(); + private final int mImageRounding; + private final int mMaxImageHeight; + private final int mIsolatedSize; + private final int mExtraSpacing; + private Drawable mDrawable; + private float mAspectRatio; + private int mActualWidth; + private int mActualHeight; + private boolean mIsIsolated; + + public MessagingImageMessage(@NonNull Context context) { + this(context, null); + } + + public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mMinImageHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_min_size); + mMaxImageHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_max_height); + mImageRounding = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_rounding); + mExtraSpacing = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_extra_spacing); + setMaxHeight(mMaxImageHeight); + mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size); + } + + @Override + public MessagingMessageState getState() { + return mState; + } + + @Override + public boolean setMessage(Notification.MessagingStyle.Message message) { + MessagingMessage.super.setMessage(message); + Drawable drawable; + try { + drawable = LocalImageResolver.resolveImage(message.getDataUri(), getContext()); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + int intrinsicHeight = drawable.getIntrinsicHeight(); + if (intrinsicHeight == 0) { + Log.w(TAG, "Drawable with 0 intrinsic height was returned"); + return false; + } + mDrawable = drawable; + mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight; + setImageDrawable(drawable); + setContentDescription(message.getText()); + return true; + } + + static MessagingMessage createMessage(MessagingLayout layout, + Notification.MessagingStyle.Message m) { + MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); + MessagingImageMessage createdMessage = sInstancePool.acquire(); + if (createdMessage == null) { + createdMessage = (MessagingImageMessage) LayoutInflater.from( + layout.getContext()).inflate( + R.layout.notification_template_messaging_image_message, + messagingLinearLayout, + false); + createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); + } + boolean created = createdMessage.setMessage(m); + if (!created) { + createdMessage.recycle(); + return MessagingTextMessage.createMessage(layout, m); + } + return createdMessage; + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.save(); + canvas.clipPath(getRoundedRectPath()); + int width = (int) Math.max(getActualWidth(), getActualHeight() * mAspectRatio); + int height = (int) (width / mAspectRatio); + int left = (int) ((getActualWidth() - width) / 2.0f); + mDrawable.setBounds(left, 0, left + width, height); + mDrawable.draw(canvas); + canvas.restore(); + } + + public Path getRoundedRectPath() { + int left = 0; + int right = getActualWidth(); + int top = 0; + int bottom = getActualHeight(); + mPath.reset(); + int width = right - left; + float roundnessX = mImageRounding; + float roundnessY = mImageRounding; + roundnessX = Math.min(width / 2, roundnessX); + roundnessY = Math.min((bottom - top) / 2, roundnessY); + mPath.moveTo(left, top + roundnessY); + mPath.quadTo(left, top, left + roundnessX, top); + mPath.lineTo(right - roundnessX, top); + mPath.quadTo(right, top, right, top + roundnessY); + mPath.lineTo(right, bottom - roundnessY); + mPath.quadTo(right, bottom, right - roundnessX, bottom); + mPath.lineTo(left + roundnessX, bottom); + mPath.quadTo(left, bottom, left, bottom - roundnessY); + mPath.close(); + return mPath; + } + + public void recycle() { + MessagingMessage.super.recycle(); + setAlpha(1.0f); + setTranslationY(0); + setImageBitmap(null); + mDrawable = null; + sInstancePool.release(this); + } + + public static void dropCache() { + sInstancePool = new Pools.SynchronizedPool<>(10); + } + + @Override + public int getMeasuredType() { + int measuredHeight = getMeasuredHeight(); + int minImageHeight; + if (mIsIsolated) { + minImageHeight = mIsolatedSize; + } else { + minImageHeight = mMinImageHeight; + } + boolean measuredTooSmall = measuredHeight < minImageHeight + && measuredHeight != mDrawable.getIntrinsicHeight(); + if (measuredTooSmall) { + return MEASURED_TOO_SMALL; + } else { + if (!mIsIsolated && measuredHeight != mDrawable.getIntrinsicHeight()) { + return MEASURED_SHORTENED; + } else { + return MEASURED_NORMAL; + } + } + } + + @Override + public void setMaxDisplayedLines(int lines) { + // Nothing to do, this should be handled automatically. + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mIsIsolated) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + // TODO: ensure that this isn't called when transforming + setActualWidth(getStaticWidth()); + setActualHeight(getHeight()); + } + + @Override + public int getConsumedLines() { + return 3; + } + + public void setActualWidth(int actualWidth) { + mActualWidth = actualWidth; + invalidate(); + } + + public int getActualWidth() { + return mActualWidth; + } + + public void setActualHeight(int actualHeight) { + mActualHeight = actualHeight; + invalidate(); + } + + public int getActualHeight() { + return mActualHeight; + } + + public int getStaticWidth() { + if (mIsIsolated) { + return getWidth(); + } + return (int) (getHeight() * mAspectRatio); + } + + public void setIsolated(boolean isolated) { + if (mIsIsolated != isolated) { + mIsIsolated = isolated; + // update the layout params not to have margins + ViewGroup.MarginLayoutParams layoutParams = + (ViewGroup.MarginLayoutParams) getLayoutParams(); + layoutParams.topMargin = isolated ? 0 : mExtraSpacing; + setLayoutParams(layoutParams); + } + } + + @Override + public int getExtraSpacing() { + return mExtraSpacing; + } +} diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index d45c086e3b52..5279636ae2a0 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -81,6 +81,7 @@ public class MessagingLayout extends FrameLayout { private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>(); private Notification.Person mUser; private CharSequence mNameReplacement; + private boolean mIsCollapsed; public MessagingLayout(@NonNull Context context) { super(context); @@ -127,6 +128,11 @@ public class MessagingLayout extends FrameLayout { } @RemotableViewMethod + public void setIsCollapsed(boolean isCollapsed) { + mIsCollapsed = isCollapsed; + } + + @RemotableViewMethod public void setData(Bundle extras) { Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); List<Notification.MessagingStyle.Message> newMessages @@ -331,6 +337,7 @@ public class MessagingLayout extends FrameLayout { newGroup = MessagingGroup.createGroup(mMessagingLinearLayout); mAddedGroups.add(newGroup); } + newGroup.setDisplayAvatarsAtEnd(mIsCollapsed); newGroup.setLayoutColor(mLayoutColor); Notification.Person sender = senders.get(groupIndex); CharSequence nameOverride = null; @@ -392,7 +399,6 @@ public class MessagingLayout extends FrameLayout { MessagingMessage message = findAndRemoveMatchingMessage(m); if (message == null) { message = MessagingMessage.createMessage(this, m); - message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR); } message.setIsHistoric(historic); result.add(message); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index f0ef37076618..991e3e7a80f9 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -75,7 +75,6 @@ public class MessagingLinearLayout extends ViewGroup { targetHeight = Integer.MAX_VALUE; break; } - int widthSize = MeasureSpec.getSize(widthMeasureSpec); // Now that we know which views to take, fix up the indents and see what width we get. int measuredWidth = mPaddingLeft + mPaddingRight; @@ -90,7 +89,6 @@ public class MessagingLinearLayout extends ViewGroup { totalHeight = mPaddingTop + mPaddingBottom; boolean first = true; int linesRemaining = mMaxDisplayedLines; - // Starting from the bottom: we measure every view as if it were the only one. If it still // fits, we take it, otherwise we stop there. for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { @@ -100,11 +98,13 @@ public class MessagingLinearLayout extends ViewGroup { final View child = getChildAt(i); LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); MessagingChild messagingChild = null; + int spacing = mSpacing; if (child instanceof MessagingChild) { messagingChild = (MessagingChild) child; messagingChild.setMaxDisplayedLines(linesRemaining); + spacing += messagingChild.getExtraSpacing(); } - int spacing = first ? 0 : mSpacing; + spacing = first ? 0 : spacing; measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight - mPaddingTop - mPaddingBottom + spacing); @@ -254,6 +254,9 @@ public class MessagingLinearLayout extends ViewGroup { void setMaxDisplayedLines(int lines); void hideAnimated(); boolean isHidingAnimated(); + default int getExtraSpacing() { + return 0; + } } public static class LayoutParams extends MarginLayoutParams { diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index f09621f544bc..bf1c5ca747a4 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -16,182 +16,125 @@ package com.android.internal.widget; -import android.annotation.AttrRes; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StyleRes; import android.app.Notification; -import android.content.Context; -import android.text.Layout; -import android.util.AttributeSet; -import android.util.Pools; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.RemoteViews; - -import com.android.internal.R; +import android.view.View; import java.util.Objects; /** * A message of a {@link MessagingLayout}. */ -@RemoteViews.RemoteView -public class MessagingMessage extends ImageFloatingTextView implements - MessagingLinearLayout.MessagingChild { - - private static Pools.SimplePool<MessagingMessage> sInstancePool - = new Pools.SynchronizedPool<>(10); - private Notification.MessagingStyle.Message mMessage; - private MessagingGroup mGroup; - private boolean mIsHistoric; - private boolean mIsHidingAnimated; +public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { - public MessagingMessage(@NonNull Context context) { - super(context); - } + /** + * Prefix for supported image MIME types + **/ + String IMAGE_MIME_TYPE_PREFIX = "image/"; - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); + static MessagingMessage createMessage(MessagingLayout layout, + Notification.MessagingStyle.Message m) { + if (hasImage(m)) { + return MessagingImageMessage.createMessage(layout, m); + } else { + return MessagingTextMessage.createMessage(layout, m); + } } - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { - super(context, attrs, defStyleAttr); + static void dropCache() { + MessagingTextMessage.dropCache(); + MessagingImageMessage.dropCache(); } - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + static boolean hasImage(Notification.MessagingStyle.Message m) { + return m.getDataUri() != null + && m.getDataMimeType() != null + && m.getDataMimeType().startsWith(IMAGE_MIME_TYPE_PREFIX); } - private void setMessage(Notification.MessagingStyle.Message message) { - mMessage = message; - setText(message.getText()); + /** + * Set a message for this view. + * @return true if setting the message worked + */ + default boolean setMessage(Notification.MessagingStyle.Message message) { + getState().setMessage(message); + return true; } - public Notification.MessagingStyle.Message getMessage() { - return mMessage; + default Notification.MessagingStyle.Message getMessage() { + return getState().getMessage(); } - boolean sameAs(Notification.MessagingStyle.Message message) { - if (!Objects.equals(message.getText(), mMessage.getText())) { + default boolean sameAs(Notification.MessagingStyle.Message message) { + Notification.MessagingStyle.Message ownMessage = getMessage(); + if (!Objects.equals(message.getText(), ownMessage.getText())) { return false; } - if (!Objects.equals(message.getSender(), mMessage.getSender())) { + if (!Objects.equals(message.getSender(), ownMessage.getSender())) { return false; } - if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) { + if (!Objects.equals(message.getTimestamp(), ownMessage.getTimestamp())) { + return false; + } + if (!Objects.equals(message.getDataMimeType(), ownMessage.getDataMimeType())) { + return false; + } + if (!Objects.equals(message.getDataUri(), ownMessage.getDataUri())) { return false; } return true; } - boolean sameAs(MessagingMessage message) { + default boolean sameAs(MessagingMessage message) { return sameAs(message.getMessage()); } - static MessagingMessage createMessage(MessagingLayout layout, - Notification.MessagingStyle.Message m) { - MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); - MessagingMessage createdMessage = sInstancePool.acquire(); - if (createdMessage == null) { - createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate( - R.layout.notification_template_messaging_message, messagingLinearLayout, - false); - } - createdMessage.setMessage(m); - return createdMessage; - } - - public void removeMessage() { - mGroup.removeMessage(this); - } - - public void recycle() { - mGroup = null; - mMessage = null; - setAlpha(1.0f); - setTranslationY(0); - sInstancePool.release(this); + default void removeMessage() { + getGroup().removeMessage(this); } - public void setMessagingGroup(MessagingGroup group) { - mGroup = group; + default void setMessagingGroup(MessagingGroup group) { + getState().setGroup(group); } - public static void dropCache() { - sInstancePool = new Pools.SynchronizedPool<>(10); + default void setIsHistoric(boolean isHistoric) { + getState().setIsHistoric(isHistoric); } - public void setIsHistoric(boolean isHistoric) { - mIsHistoric = isHistoric; + default MessagingGroup getGroup() { + return getState().getGroup(); } - public MessagingGroup getGroup() { - return mGroup; + default void setIsHidingAnimated(boolean isHiding) { + getState().setIsHidingAnimated(isHiding); } @Override - public int getMeasuredType() { - boolean measuredTooSmall = getMeasuredHeight() - < getLayoutHeight() + getPaddingTop() + getPaddingBottom(); - if (measuredTooSmall) { - return MEASURED_TOO_SMALL; - } else { - Layout layout = getLayout(); - if (layout == null) { - return MEASURED_TOO_SMALL; - } - if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) { - return MEASURED_SHORTENED; - } else { - return MEASURED_NORMAL; - } - } + default boolean isHidingAnimated() { + return getState().isHidingAnimated(); } @Override - public void hideAnimated() { + default void hideAnimated() { setIsHidingAnimated(true); - mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false)); + getGroup().performRemoveAnimation(getState().getHostView(), + () -> setIsHidingAnimated(false)); } - private void setIsHidingAnimated(boolean isHiding) { - ViewParent parent = getParent(); - mIsHidingAnimated = isHiding; - invalidate(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).invalidate(); - } + default boolean hasOverlappingRendering() { + return false; } - @Override - public boolean isHidingAnimated() { - return mIsHidingAnimated; + default void recycle() { + getState().reset(); } - @Override - public void setMaxDisplayedLines(int lines) { - setMaxLines(lines); + default View getView() { + return (View) this; } - @Override - public int getConsumedLines() { - return getLineCount(); - } + default void setColor(int textColor) {} - public int getLayoutHeight() { - Layout layout = getLayout(); - if (layout == null) { - return 0; - } - return layout.getHeight(); - } + MessagingMessageState getState(); - @Override - public boolean hasOverlappingRendering() { - return false; - } + void setVisibility(int visibility); } diff --git a/core/java/com/android/internal/widget/MessagingMessageState.java b/core/java/com/android/internal/widget/MessagingMessageState.java new file mode 100644 index 000000000000..ac624728689d --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingMessageState.java @@ -0,0 +1,81 @@ +/* + * 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.internal.widget; + +import android.app.Notification; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +/** + * Shared state and implementation for MessagingMessages. Used to share common implementations. + */ +public class MessagingMessageState { + private final View mHostView; + private Notification.MessagingStyle.Message mMessage; + private MessagingGroup mGroup; + private boolean mIsHistoric; + private boolean mIsHidingAnimated; + + MessagingMessageState(View hostView) { + mHostView = hostView; + } + + public void setMessage(Notification.MessagingStyle.Message message) { + mMessage = message; + } + + public Notification.MessagingStyle.Message getMessage() { + return mMessage; + } + + public void setGroup(MessagingGroup group) { + mGroup = group; + } + + public MessagingGroup getGroup() { + return mGroup; + } + + public void setIsHistoric(boolean isHistoric) { + mIsHistoric = isHistoric; + } + + public void setIsHidingAnimated(boolean isHiding) { + ViewParent parent = mHostView.getParent(); + mIsHidingAnimated = isHiding; + mHostView.invalidate(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).invalidate(); + } + } + + public boolean isHidingAnimated() { + return mIsHidingAnimated; + } + + public View getHostView() { + return mHostView; + } + + public void reset() { + mIsHidingAnimated = false; + mIsHistoric = false; + mGroup = null; + mMessage = null; + } +} diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java new file mode 100644 index 000000000000..794cc1dc66f7 --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingTextMessage.java @@ -0,0 +1,145 @@ +/* + * 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.internal.widget; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StyleRes; +import android.app.Notification; +import android.content.Context; +import android.text.Layout; +import android.util.AttributeSet; +import android.util.Pools; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.RemoteViews; + +import com.android.internal.R; + +import java.util.Objects; + +/** + * A message of a {@link MessagingLayout}. + */ +@RemoteViews.RemoteView +public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage { + + private static Pools.SimplePool<MessagingTextMessage> sInstancePool + = new Pools.SynchronizedPool<>(20); + private final MessagingMessageState mState = new MessagingMessageState(this); + + public MessagingTextMessage(@NonNull Context context) { + super(context); + } + + public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public MessagingMessageState getState() { + return mState; + } + + @Override + public boolean setMessage(Notification.MessagingStyle.Message message) { + MessagingMessage.super.setMessage(message); + setText(message.getText()); + return true; + } + + static MessagingMessage createMessage(MessagingLayout layout, + Notification.MessagingStyle.Message m) { + MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); + MessagingTextMessage createdMessage = sInstancePool.acquire(); + if (createdMessage == null) { + createdMessage = (MessagingTextMessage) LayoutInflater.from( + layout.getContext()).inflate( + R.layout.notification_template_messaging_text_message, + messagingLinearLayout, + false); + createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); + } + createdMessage.setMessage(m); + return createdMessage; + } + + public void recycle() { + MessagingMessage.super.recycle(); + setAlpha(1.0f); + setTranslationY(0); + sInstancePool.release(this); + } + + public static void dropCache() { + sInstancePool = new Pools.SynchronizedPool<>(10); + } + + @Override + public int getMeasuredType() { + boolean measuredTooSmall = getMeasuredHeight() + < getLayoutHeight() + getPaddingTop() + getPaddingBottom(); + if (measuredTooSmall) { + return MEASURED_TOO_SMALL; + } else { + Layout layout = getLayout(); + if (layout == null) { + return MEASURED_TOO_SMALL; + } + if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) { + return MEASURED_SHORTENED; + } else { + return MEASURED_NORMAL; + } + } + } + + @Override + public void setMaxDisplayedLines(int lines) { + setMaxLines(lines); + } + + @Override + public int getConsumedLines() { + return getLineCount(); + } + + public int getLayoutHeight() { + Layout layout = getLayout(); + if (layout == null) { + return 0; + } + return layout.getHeight(); + } + + @Override + public void setColor(int color) { + setTextColor(color); + } +} diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index fb186693979d..efd98e2ba96b 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -164,7 +164,7 @@ public class BootReceiver extends BroadcastReceiver { .append("Revision: ") .append(SystemProperties.get("ro.revision", "")).append("\n") .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") - .append("Radio: ").append(Build.RADIO).append("\n") + .append("Radio: ").append(Build.getRadioVersion()).append("\n") .append("Kernel: ") .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) .append("\n").toString(); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 33f80ce8ffb0..78a3e137ccb0 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -110,8 +110,8 @@ cc_library_shared { "android_util_AssetManager.cpp", "android_util_Binder.cpp", "android_util_EventLog.cpp", - "android_util_MemoryIntArray.cpp", "android_util_Log.cpp", + "android_util_MemoryIntArray.cpp", "android_util_PathParser.cpp", "android_util_Process.cpp", "android_util_StringBlock.cpp", @@ -191,6 +191,7 @@ cc_library_shared { "android_backup_FileBackupHelperBase.cpp", "android_backup_BackupHelperDispatcher.cpp", "android_app_backup_FullBackup.cpp", + "android_content_res_ApkAssets.cpp", "android_content_res_ObbScanner.cpp", "android_content_res_Configuration.cpp", "android_animation_PropertyValuesHolder.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d20217386b1e..4a032c4bbae4 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -123,6 +123,7 @@ extern int register_android_util_MemoryIntArray(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_content_StringBlock(JNIEnv* env); extern int register_android_content_XmlBlock(JNIEnv* env); +extern int register_android_content_res_ApkAssets(JNIEnv* env); extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); @@ -1346,6 +1347,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), REG_JNI(register_android_content_XmlBlock), + REG_JNI(register_android_content_res_ApkAssets), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), REG_JNI(register_android_text_MeasuredParagraph), diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 937b3ffb9d60..f6223fad3dcb 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -28,7 +28,7 @@ #include <nativehelper/ScopedUtfChars.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_util_AssetManager.h> -#include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include "Utils.h" #include "FontUtils.h" @@ -217,7 +217,8 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b NPE_CHECK_RETURN_ZERO(env, jpath); NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); - AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr); + + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, jassetMgr); if (NULL == mgr) { builder->axes.clear(); return false; @@ -229,27 +230,33 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b return false; } - Asset* asset; - if (isAsset) { - asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - } else { - asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(), - Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + if (isAsset) { + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + } else if (cookie > 0) { + // Valid java cookies are 1-based, but AssetManager cookies are 0-based. + asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1), + Asset::ACCESS_BUFFER); + } else { + asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + } } - if (NULL == asset) { + if (nullptr == asset) { builder->axes.clear(); return false; } const void* buf = asset->getBuffer(false); if (NULL == buf) { - delete asset; builder->axes.clear(); return false; } - sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset)); + sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, + asset.release())); return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); } diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index 09e37e1a3de6..49a24a30f77e 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -361,7 +361,7 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName code->sdkVersion = sdkVersion; code->javaAssetManager = env->NewGlobalRef(jAssetMgr); - code->assetManager = assetManagerForJavaObject(env, jAssetMgr); + code->assetManager = NdkAssetManagerForJavaObject(env, jAssetMgr); if (obbDir != NULL) { dirStr = env->GetStringUTFChars(obbDir, NULL); diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp new file mode 100644 index 000000000000..c0f151b71c93 --- /dev/null +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "android-base/unique_fd.h" +#include "androidfw/ApkAssets.h" +#include "utils/misc.h" + +#include "core_jni_helpers.h" +#include "jni.h" +#include "nativehelper/ScopedUtfChars.h" + +using ::android::base::unique_fd; + +namespace android { + +static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, + jboolean force_shared_lib, jboolean overlay) { + ScopedUtfChars path(env, java_path); + if (path.c_str() == nullptr) { + return 0; + } + + std::unique_ptr<const ApkAssets> apk_assets; + if (overlay) { + apk_assets = ApkAssets::LoadOverlay(path.c_str(), system); + } else if (force_shared_lib) { + apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system); + } else { + apk_assets = ApkAssets::Load(path.c_str(), system); + } + + if (apk_assets == nullptr) { + std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str()); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } + return reinterpret_cast<jlong>(apk_assets.release()); +} + +static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor, + jstring friendly_name, jboolean system, jboolean force_shared_lib) { + ScopedUtfChars friendly_name_utf8(env, friendly_name); + if (friendly_name_utf8.c_str() == nullptr) { + return 0; + } + + int fd = jniGetFDFromFileDescriptor(env, file_descriptor); + if (fd < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); + return 0; + } + + unique_fd dup_fd(::dup(fd)); + if (dup_fd < 0) { + jniThrowIOException(env, errno); + return 0; + } + + std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), + friendly_name_utf8.c_str(), + system, force_shared_lib); + if (apk_assets == nullptr) { + std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d", + friendly_name_utf8.c_str(), dup_fd.get()); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } + return reinterpret_cast<jlong>(apk_assets.release()); +} + +static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + delete reinterpret_cast<ApkAssets*>(ptr); +} + +static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return env->NewStringUTF(apk_assets->GetPath().c_str()); +} + +static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool()); +} + +static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + (void)apk_assets; + return JNI_TRUE; +} + +static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { + ScopedUtfChars path_utf8(env, file_name); + if (path_utf8.c_str() == nullptr) { + return 0; + } + + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(), + Asset::AccessMode::ACCESS_RANDOM); + if (asset == nullptr) { + jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str()); + return 0; + } + + // DynamicRefTable is only needed when looking up resource references. Opening an XML file + // directly from an ApkAssets has no notion of proper resource references. + std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/); + status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); + asset.reset(); + + if (err != NO_ERROR) { + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + return 0; + } + return reinterpret_cast<jlong>(xml_tree.release()); +} + +// JNI registration. +static const JNINativeMethod gApkAssetsMethods[] = { + {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad}, + {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J", + (void*)NativeLoadFromFd}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, + {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, + {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, + {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, +}; + +int register_android_content_res_ApkAssets(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods, + arraysize(gApkAssetsMethods)); +} + +} // namespace android diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 683b4c490ec3..c623ca621292 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -1,1851 +1,1441 @@ -/* //device/libs/android_runtime/android_util_AssetManager.cpp -** -** Copyright 2006, 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. -*/ +/* + * Copyright 2006, 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. + */ #define LOG_TAG "asset" -#include <android_runtime/android_util_AssetManager.h> - #include <inttypes.h> #include <linux/capability.h> #include <stdio.h> -#include <sys/types.h> -#include <sys/wait.h> #include <sys/stat.h> #include <sys/system_properties.h> +#include <sys/types.h> +#include <sys/wait.h> #include <private/android_filesystem_config.h> // for AID_SYSTEM +#include "android-base/logging.h" +#include "android-base/properties.h" +#include "android-base/stringprintf.h" +#include "android_runtime/android_util_AssetManager.h" +#include "android_runtime/AndroidRuntime.h" +#include "android_util_Binder.h" #include "androidfw/Asset.h" #include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeResolution.h" +#include "androidfw/MutexGuard.h" #include "androidfw/ResourceTypes.h" -#include "android_runtime/AndroidRuntime.h" -#include "android_util_Binder.h" #include "core_jni_helpers.h" #include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedStringChars.h> -#include <nativehelper/ScopedUtfChars.h> +#include "nativehelper/JNIHelp.h" +#include "nativehelper/ScopedPrimitiveArray.h" +#include "nativehelper/ScopedStringChars.h" +#include "nativehelper/ScopedUtfChars.h" #include "utils/Log.h" -#include "utils/misc.h" #include "utils/String8.h" +#include "utils/misc.h" extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap); extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap); +using ::android::base::StringPrintf; namespace android { -static const bool kThrowOnBadId = false; - // ---------------------------------------------------------------------------- -static struct typedvalue_offsets_t -{ - jfieldID mType; - jfieldID mData; - jfieldID mString; - jfieldID mAssetCookie; - jfieldID mResourceId; - jfieldID mChangingConfigurations; - jfieldID mDensity; +static struct typedvalue_offsets_t { + jfieldID mType; + jfieldID mData; + jfieldID mString; + jfieldID mAssetCookie; + jfieldID mResourceId; + jfieldID mChangingConfigurations; + jfieldID mDensity; } gTypedValueOffsets; -static struct assetfiledescriptor_offsets_t -{ - jfieldID mFd; - jfieldID mStartOffset; - jfieldID mLength; +static struct assetfiledescriptor_offsets_t { + jfieldID mFd; + jfieldID mStartOffset; + jfieldID mLength; } gAssetFileDescriptorOffsets; -static struct assetmanager_offsets_t -{ - jfieldID mObject; +static struct assetmanager_offsets_t { + jfieldID mObject; } gAssetManagerOffsets; -static struct sparsearray_offsets_t -{ - jclass classObject; - jmethodID constructor; - jmethodID put; +static struct { + jfieldID native_ptr; +} gApkAssetsFields; + +static struct sparsearray_offsets_t { + jclass classObject; + jmethodID constructor; + jmethodID put; } gSparseArrayOffsets; -static struct configuration_offsets_t -{ - jclass classObject; - jmethodID constructor; - jfieldID mSmallestScreenWidthDpOffset; - jfieldID mScreenWidthDpOffset; - jfieldID mScreenHeightDpOffset; +static struct configuration_offsets_t { + jclass classObject; + jmethodID constructor; + jfieldID mSmallestScreenWidthDpOffset; + jfieldID mScreenWidthDpOffset; + jfieldID mScreenHeightDpOffset; } gConfigurationOffsets; -jclass g_stringClass = NULL; +jclass g_stringClass = nullptr; // ---------------------------------------------------------------------------- -static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table, - const Res_value& value, uint32_t ref, ssize_t block, - uint32_t typeSpecFlags, ResTable_config* config = NULL); - -jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table, - const Res_value& value, uint32_t ref, ssize_t block, - uint32_t typeSpecFlags, ResTable_config* config) -{ - env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType); - env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie, - static_cast<jint>(table->getTableCookie(block))); - env->SetIntField(outValue, gTypedValueOffsets.mData, value.data); - env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL); - env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref); - env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations, - typeSpecFlags); - if (config != NULL) { - env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density); - } - return block; +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +constexpr inline static jint ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<jint>(cookie + 1) : -1; } -// This is called by zygote (running as user root) as part of preloadResources. -static void verifySystemIdmaps() -{ - pid_t pid; - char system_id[10]; - - snprintf(system_id, sizeof(system_id), "%d", AID_SYSTEM); - - switch (pid = fork()) { - case -1: - ALOGE("failed to fork for idmap: %s", strerror(errno)); - break; - case 0: // child - { - struct __user_cap_header_struct capheader; - struct __user_cap_data_struct capdata; - - memset(&capheader, 0, sizeof(capheader)); - memset(&capdata, 0, sizeof(capdata)); - - capheader.version = _LINUX_CAPABILITY_VERSION; - capheader.pid = 0; - - if (capget(&capheader, &capdata) != 0) { - ALOGE("capget: %s\n", strerror(errno)); - exit(1); - } - - capdata.effective = capdata.permitted; - if (capset(&capheader, &capdata) != 0) { - ALOGE("capset: %s\n", strerror(errno)); - exit(1); - } - - if (setgid(AID_SYSTEM) != 0) { - ALOGE("setgid: %s\n", strerror(errno)); - exit(1); - } - - if (setuid(AID_SYSTEM) != 0) { - ALOGE("setuid: %s\n", strerror(errno)); - exit(1); - } - - // Generic idmap parameters - const char* argv[8]; - int argc = 0; - struct stat st; - - memset(argv, NULL, sizeof(argv)); - argv[argc++] = AssetManager::IDMAP_BIN; - argv[argc++] = "--scan"; - argv[argc++] = AssetManager::TARGET_PACKAGE_NAME; - argv[argc++] = AssetManager::TARGET_APK_PATH; - argv[argc++] = AssetManager::IDMAP_DIR; - - // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined, - // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR. - char subdir[PROP_VALUE_MAX]; - int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir); - if (len > 0) { - String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir; - if (stat(overlayPath.string(), &st) == 0) { - argv[argc++] = overlayPath.string(); - } - } - if (stat(AssetManager::OVERLAY_DIR, &st) == 0) { - argv[argc++] = AssetManager::OVERLAY_DIR; - } - - if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) { - argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR; - } - - // Finally, invoke idmap (if any overlay directory exists) - if (argc > 5) { - execv(AssetManager::IDMAP_BIN, (char* const*)argv); - ALOGE("failed to execv for idmap: %s", strerror(errno)); - exit(1); // should never get here - } else { - exit(0); - } - } - break; - default: // parent - waitpid(pid, NULL, 0); - break; - } +constexpr inline static ApkAssetsCookie JavaCookieToApkAssetsCookie(jint cookie) { + return cookie > 0 ? static_cast<ApkAssetsCookie>(cookie - 1) : kInvalidCookie; } - -// ---------------------------------------------------------------------------- - -// this guy is exported to other jni routines -AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj) -{ - jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject); - AssetManager* am = reinterpret_cast<AssetManager*>(amHandle); - if (am != NULL) { - return am; - } - jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!"); - return NULL; +// This is called by zygote (running as user root) as part of preloadResources. +static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { + switch (pid_t pid = fork()) { + case -1: + PLOG(ERROR) << "failed to fork for idmap"; + break; + + // child + case 0: { + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata; + + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + + capheader.version = _LINUX_CAPABILITY_VERSION; + capheader.pid = 0; + + if (capget(&capheader, &capdata) != 0) { + PLOG(ERROR) << "capget"; + exit(1); + } + + capdata.effective = capdata.permitted; + if (capset(&capheader, &capdata) != 0) { + PLOG(ERROR) << "capset"; + exit(1); + } + + if (setgid(AID_SYSTEM) != 0) { + PLOG(ERROR) << "setgid"; + exit(1); + } + + if (setuid(AID_SYSTEM) != 0) { + PLOG(ERROR) << "setuid"; + exit(1); + } + + // Generic idmap parameters + const char* argv[8]; + int argc = 0; + struct stat st; + + memset(argv, 0, sizeof(argv)); + argv[argc++] = AssetManager::IDMAP_BIN; + argv[argc++] = "--scan"; + argv[argc++] = AssetManager::TARGET_PACKAGE_NAME; + argv[argc++] = AssetManager::TARGET_APK_PATH; + argv[argc++] = AssetManager::IDMAP_DIR; + + // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined, + // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR. + std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY, + ""); + if (!overlay_theme_path.empty()) { + overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path; + if (stat(overlay_theme_path.c_str(), &st) == 0) { + argv[argc++] = overlay_theme_path.c_str(); + } + } + + if (stat(AssetManager::OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::OVERLAY_DIR; + } + + if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR; + } + + // Finally, invoke idmap (if any overlay directory exists) + if (argc > 5) { + execv(AssetManager::IDMAP_BIN, (char* const*)argv); + PLOG(ERROR) << "failed to execv for idmap"; + exit(1); // should never get here + } else { + exit(0); + } + } break; + + // parent + default: + waitpid(pid, nullptr, 0); + break; + } } -static jlong android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz, - jstring fileName, jint mode) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openAsset in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Empty file name"); - return -1; - } - - if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM - && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); - return -1; - } - - Asset* a = am->open(fileName8.c_str(), (Asset::AccessMode)mode); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return -1; - } - - //printf("Created Asset Stream: %p\n", a); - - return reinterpret_cast<jlong>(a); +static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref, + uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) { + env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType); + env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie, + ApkAssetsCookieToJavaCookie(cookie)); + env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data); + env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr); + env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref); + env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags); + if (config != nullptr) { + env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density); + } + return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie)); } -static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outOffsets) -{ - off64_t startOffset, length; - int fd = a->openFileDescriptor(&startOffset, &length); - delete a; - - if (fd < 0) { - jniThrowException(env, "java/io/FileNotFoundException", - "This file can not be opened as a file descriptor; it is probably compressed"); - return NULL; - } - - jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0); - if (offsets == NULL) { - close(fd); - return NULL; - } - - offsets[0] = startOffset; - offsets[1] = length; - - env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0); +// ---------------------------------------------------------------------------- - jobject fileDesc = jniCreateFileDescriptor(env, fd); - if (fileDesc == NULL) { - close(fd); - return NULL; - } +// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance. +struct GuardedAssetManager : public ::AAssetManager { + Guarded<AssetManager2> guarded_assetmanager; +}; - return newParcelFileDescriptor(env, fileDesc); +::AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) { + jlong assetmanager_handle = env->GetLongField(jassetmanager, gAssetManagerOffsets.mObject); + ::AAssetManager* am = reinterpret_cast<::AAssetManager*>(assetmanager_handle); + if (am == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!"); + return nullptr; + } + return am; } -static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject clazz, - jstring fileName, jlongArray outOffsets) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ALOGV("openAssetFd in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - Asset* a = am->open(fileName8.c_str(), Asset::ACCESS_RANDOM); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - //printf("Created Asset Stream: %p\n", a); - - return returnParcelFileDescriptor(env, a, outOffsets); +Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) { + if (assetmanager == nullptr) { + return nullptr; + } + return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager; } -static jlong android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName, - jint mode) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return -1; - } - - if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM - && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); - return -1; - } - - Asset* a = cookie - ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), - (Asset::AccessMode)mode) - : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return -1; - } - - //printf("Created Asset Stream: %p\n", a); - - return reinterpret_cast<jlong>(a); +Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) { + return AssetManagerForNdkAssetManager(NdkAssetManagerForJavaObject(env, jassetmanager)); } -static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName, - jlongArray outOffsets) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ALOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - Asset* a = cookie - ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_RANDOM) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_RANDOM); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - //printf("Created Asset Stream: %p\n", a); - - return returnParcelFileDescriptor(env, a, outOffsets); +static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) { + return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr)); } -static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz, - jstring fileName) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - AssetDir* dir = am->openDir(fileName8.c_str()); - - if (dir == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - size_t N = dir->getFileCount(); - - jobjectArray array = env->NewObjectArray(dir->getFileCount(), - g_stringClass, NULL); - if (array == NULL) { - delete dir; - return NULL; - } - - for (size_t i=0; i<N; i++) { - const String8& name = dir->getFileName(i); - jstring str = env->NewStringUTF(name.string()); - if (str == NULL) { - delete dir; - return NULL; - } - env->SetObjectArrayElement(array, i, str); - env->DeleteLocalRef(str); - } - - delete dir; - - return array; +static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset, + jlongArray out_offsets) { + off64_t start_offset, length; + int fd = asset->openFileDescriptor(&start_offset, &length); + asset.reset(); + + if (fd < 0) { + jniThrowException(env, "java/io/FileNotFoundException", + "This file can not be opened as a file descriptor; it is probably " + "compressed"); + return nullptr; + } + + jlong* offsets = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(out_offsets, 0)); + if (offsets == nullptr) { + close(fd); + return nullptr; + } + + offsets[0] = start_offset; + offsets[1] = length; + + env->ReleasePrimitiveArrayCritical(out_offsets, offsets, 0); + + jobject file_desc = jniCreateFileDescriptor(env, fd); + if (file_desc == nullptr) { + close(fd); + return nullptr; + } + return newParcelFileDescriptor(env, file_desc); } -static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - //printf("Destroying Asset Stream: %p\n", a); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return; - } - - delete a; +static jint NativeGetGlobalAssetCount(JNIEnv* /*env*/, jobject /*clazz*/) { + return Asset::getGlobalCount(); } -static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - uint8_t b; - ssize_t res = a->read(&b, 1); - return res == 1 ? b : -1; +static jobject NativeGetAssetAllocations(JNIEnv* env, jobject /*clazz*/) { + String8 alloc = Asset::getAssetAllocations(); + if (alloc.length() <= 0) { + return nullptr; + } + return env->NewStringUTF(alloc.string()); } -static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz, - jlong assetHandle, jbyteArray bArray, - jint off, jint len) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL || bArray == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - if (len == 0) { - return 0; - } - - jsize bLen = env->GetArrayLength(bArray); - if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", ""); - return -1; - } - - jbyte* b = env->GetByteArrayElements(bArray, NULL); - ssize_t res = a->read(b+off, len); - env->ReleaseByteArrayElements(bArray, b, 0); - - if (res > 0) return static_cast<jint>(res); - - if (res < 0) { - jniThrowException(env, "java/io/IOException", ""); - } - return -1; +static jint NativeGetGlobalAssetManagerCount(JNIEnv* /*env*/, jobject /*clazz*/) { + // TODO(adamlesinski): Switch to AssetManager2. + return AssetManager::getGlobalCount(); } -static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz, - jlong assetHandle, - jlong offset, jint whence) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - return a->seek( - offset, (whence > 0) ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR)); +static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) { + // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and + // AssetManager2 in a contiguous block (GuardedAssetManager). + return reinterpret_cast<jlong>(new GuardedAssetManager()); } -static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - return a->getLength(); +static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + delete reinterpret_cast<GuardedAssetManager*>(ptr); } -static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; +static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jobjectArray apk_assets_array, jboolean invalidate_caches) { + const jsize apk_assets_len = env->GetArrayLength(apk_assets_array); + std::vector<const ApkAssets*> apk_assets; + apk_assets.reserve(apk_assets_len); + for (jsize i = 0; i < apk_assets_len; i++) { + jobject obj = env->GetObjectArrayElement(apk_assets_array, i); + if (obj == nullptr) { + std::string msg = StringPrintf("ApkAssets at index %d is null", i); + jniThrowNullPointerException(env, msg.c_str()); + return; } - return a->getRemainingLength(); -} - -static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, - jstring path, jboolean appAsLib) -{ - ScopedUtfChars path8(env, path); - if (path8.c_str() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; + jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr); + if (env->ExceptionCheck()) { + return; } + apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr)); + } - int32_t cookie; - bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib); - - return (res) ? static_cast<jint>(cookie) : 0; + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + assetmanager->SetApkAssets(apk_assets, invalidate_caches); } -static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz, - jstring idmapPath) -{ - ScopedUtfChars idmapPath8(env, idmapPath); - if (idmapPath8.c_str() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - int32_t cookie; - bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie); - - return (res) ? (jint)cookie : 0; +static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc, + jstring locale, jint orientation, jint touchscreen, jint density, + jint keyboard, jint keyboard_hidden, jint navigation, + jint screen_width, jint screen_height, + jint smallest_screen_width_dp, jint screen_width_dp, + jint screen_height_dp, jint screen_layout, jint ui_mode, + jint color_mode, jint major_version) { + ResTable_config configuration; + memset(&configuration, 0, sizeof(configuration)); + configuration.mcc = static_cast<uint16_t>(mcc); + configuration.mnc = static_cast<uint16_t>(mnc); + configuration.orientation = static_cast<uint8_t>(orientation); + configuration.touchscreen = static_cast<uint8_t>(touchscreen); + configuration.density = static_cast<uint16_t>(density); + configuration.keyboard = static_cast<uint8_t>(keyboard); + configuration.inputFlags = static_cast<uint8_t>(keyboard_hidden); + configuration.navigation = static_cast<uint8_t>(navigation); + configuration.screenWidth = static_cast<uint16_t>(screen_width); + configuration.screenHeight = static_cast<uint16_t>(screen_height); + configuration.smallestScreenWidthDp = static_cast<uint16_t>(smallest_screen_width_dp); + configuration.screenWidthDp = static_cast<uint16_t>(screen_width_dp); + configuration.screenHeightDp = static_cast<uint16_t>(screen_height_dp); + configuration.screenLayout = static_cast<uint8_t>(screen_layout); + configuration.uiMode = static_cast<uint8_t>(ui_mode); + configuration.colorMode = static_cast<uint8_t>(color_mode); + configuration.sdkVersion = static_cast<uint16_t>(major_version); + + if (locale != nullptr) { + ScopedUtfChars locale_utf8(env, locale); + CHECK(locale_utf8.c_str() != nullptr); + configuration.setBcp47Locale(locale_utf8.c_str()); + } + + // Constants duplicated from Java class android.content.res.Configuration. + static const jint kScreenLayoutRoundMask = 0x300; + static const jint kScreenLayoutRoundShift = 8; + + // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer + // in C++. We must extract the round qualifier out of the Java screenLayout and put it + // into screenLayout2. + configuration.screenLayout2 = + static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + assetmanager->SetConfiguration(configuration); } -static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jstring debugPathName, - jboolean appAsLib) -{ - ScopedUtfChars debugPathName8(env, debugPathName); +static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (fd < 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); - return 0; - } + jobject sparse_array = + env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor); - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } + if (sparse_array == nullptr) { + // An exception is pending. + return nullptr; + } - int dupfd = ::dup(fd); - if (dupfd < 0) { - jniThrowIOException(env, errno); - return 0; + assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) { + jstring jpackage_name = env->NewStringUTF(package_name.c_str()); + if (jpackage_name == nullptr) { + // An exception is pending. + return; } - int32_t cookie; - bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib); - - return (res) ? static_cast<jint>(cookie) : 0; -} - -static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_TRUE; - } - return am->isUpToDate() ? JNI_TRUE : JNI_FALSE; + env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id), + jpackage_name); + }); + return sparse_array; } -static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales) -{ - Vector<String8> locales; - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; +static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) { + ScopedUtfChars path_utf8(env, path); + if (path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + std::vector<std::string> all_file_paths; + { + StringPiece normalized_path = path_utf8.c_str(); + if (normalized_path.data()[0] == '/') { + normalized_path = normalized_path.substr(1); + } + std::string root_path = StringPrintf("assets/%s", normalized_path.data()); + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + for (const ApkAssets* assets : assetmanager->GetApkAssets()) { + assets->ForEachFile(root_path, [&](const StringPiece& file_path, FileType type) { + if (type == FileType::kFileTypeRegular) { + all_file_paths.push_back(file_path.to_string()); + } + }); } + } - am->getLocales(&locales, includeSystemLocales); + jobjectArray array = env->NewObjectArray(all_file_paths.size(), g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } - const int N = locales.size(); + jsize index = 0; + for (const std::string& file_path : all_file_paths) { + jstring java_string = env->NewStringUTF(file_path.c_str()); - jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL); - if (result == NULL) { - return NULL; + // Check for errors creating the strings (if malformed or no memory). + if (env->ExceptionCheck()) { + return nullptr; } - for (int i=0; i<N; i++) { - jstring str = env->NewStringUTF(locales[i].string()); - if (str == NULL) { - return NULL; - } - env->SetObjectArrayElement(result, i, str); - env->DeleteLocalRef(str); - } + env->SetObjectArrayElement(array, index++, java_string); - return result; + // If we have a large amount of string in our array, we might overflow the + // local reference table of the VM. + env->DeleteLocalRef(java_string); + } + return array; } -static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) -{ - return getLocales(env, clazz, true /* include system locales */); +static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path, + jint access_mode) { + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM && + access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset = + assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode)); + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + return reinterpret_cast<jlong>(asset.release()); } -static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz) -{ - return getLocales(env, clazz, false /* don't include system locales */); +static jobject NativeOpenAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path, + jlongArray out_offsets) { + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM); + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return nullptr; + } + return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets); } -static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) { - jobject result = env->NewObject(gConfigurationOffsets.classObject, - gConfigurationOffsets.constructor); - if (result == NULL) { - return NULL; - } - - env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset, - config.smallestScreenWidthDp); - env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); - env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); - - return result; +static jlong NativeOpenNonAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path, jint access_mode) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM && + access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, + static_cast<Asset::AccessMode>(access_mode)); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), + static_cast<Asset::AccessMode>(access_mode)); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + return reinterpret_cast<jlong>(asset.release()); } -static jobjectArray getSizeConfigurationsInternal(JNIEnv* env, - const Vector<ResTable_config>& configs) { - const int N = configs.size(); - jobjectArray result = env->NewObjectArray(N, gConfigurationOffsets.classObject, NULL); - if (result == NULL) { - return NULL; - } - - for (int i=0; i<N; i++) { - jobject config = constructConfigurationObject(env, configs[i]); - if (config == NULL) { - env->DeleteLocalRef(result); - return NULL; - } - - env->SetObjectArrayElement(result, i, config); - env->DeleteLocalRef(config); - } - - return result; +static jobject NativeOpenNonAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path, jlongArray out_offsets) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return nullptr; + } + return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets); } -static jobjectArray android_content_AssetManager_getSizeConfigurations(JNIEnv* env, jobject clazz) { - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - const ResTable& res(am->getResources()); - Vector<ResTable_config> configs; - res.getConfigurations(&configs, false /* ignoreMipmap */, true /* ignoreAndroidPackage */); - - return getSizeConfigurationsInternal(env, configs); +static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + + // May be nullptr. + const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie); + + std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table); + status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); + asset.reset(); + + if (err != NO_ERROR) { + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + return 0; + } + return reinterpret_cast<jlong>(xml_tree.release()); } -static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz, - jint mcc, jint mnc, - jstring locale, jint orientation, - jint touchscreen, jint density, - jint keyboard, jint keyboardHidden, - jint navigation, - jint screenWidth, jint screenHeight, - jint smallestScreenWidthDp, - jint screenWidthDp, jint screenHeightDp, - jint screenLayout, jint uiMode, - jint colorMode, jint sdkVersion) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return; - } - - ResTable_config config; - memset(&config, 0, sizeof(config)); - - const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL; - - // Constants duplicated from Java class android.content.res.Configuration. - static const jint kScreenLayoutRoundMask = 0x300; - static const jint kScreenLayoutRoundShift = 8; - - config.mcc = (uint16_t)mcc; - config.mnc = (uint16_t)mnc; - config.orientation = (uint8_t)orientation; - config.touchscreen = (uint8_t)touchscreen; - config.density = (uint16_t)density; - config.keyboard = (uint8_t)keyboard; - config.inputFlags = (uint8_t)keyboardHidden; - config.navigation = (uint8_t)navigation; - config.screenWidth = (uint16_t)screenWidth; - config.screenHeight = (uint16_t)screenHeight; - config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp; - config.screenWidthDp = (uint16_t)screenWidthDp; - config.screenHeightDp = (uint16_t)screenHeightDp; - config.screenLayout = (uint8_t)screenLayout; - config.uiMode = (uint8_t)uiMode; - config.colorMode = (uint8_t)colorMode; - config.sdkVersion = (uint16_t)sdkVersion; - config.minorVersion = 0; - - // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer - // in C++. We must extract the round qualifier out of the Java screenLayout and put it - // into screenLayout2. - config.screenLayout2 = - (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); - - am->setConfiguration(config, locale8); - - if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8); +static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jshort density, jobject typed_value, + jboolean resolve_references) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Res_value value; + ResTable_config selected_config; + uint32_t flags; + ApkAssetsCookie cookie = + assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/, + static_cast<uint16_t>(density), &value, &selected_config, &flags); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t ref = static_cast<uint32_t>(resid); + if (resolve_references) { + cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + } + return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value); } -static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz, - jstring name, - jstring defType, - jstring defPackage) -{ - ScopedStringChars name16(env, name); - if (name16.get() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - const char16_t* defType16 = reinterpret_cast<const char16_t*>(defType) - ? reinterpret_cast<const char16_t*>(env->GetStringChars(defType, NULL)) - : NULL; - jsize defTypeLen = defType - ? env->GetStringLength(defType) : 0; - const char16_t* defPackage16 = reinterpret_cast<const char16_t*>(defPackage) - ? reinterpret_cast<const char16_t*>(env->GetStringChars(defPackage, - NULL)) - : NULL; - jsize defPackageLen = defPackage - ? env->GetStringLength(defPackage) : 0; - - jint ident = am->getResources().identifierForName( - reinterpret_cast<const char16_t*>(name16.get()), name16.size(), - defType16, defTypeLen, defPackage16, defPackageLen); - - if (defPackage16) { - env->ReleaseStringChars(defPackage, - reinterpret_cast<const jchar*>(defPackage16)); - } - if (defType16) { - env->ReleaseStringChars(defType, - reinterpret_cast<const jchar*>(defType16)); - } - - return ident; +static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jint bag_entry_id, jobject typed_value) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t type_spec_flags = bag->type_spec_flags; + ApkAssetsCookie cookie = kInvalidCookie; + const Res_value* bag_value = nullptr; + for (const ResolvedBag::Entry& entry : bag) { + if (entry.key == static_cast<uint32_t>(bag_entry_id)) { + cookie = entry.cookie; + bag_value = &entry.value; + + // Keep searching (the old implementation did that). + } + } + + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + Res_value value = *bag_value; + uint32_t ref = static_cast<uint32_t>(resid); + ResTable_config selected_config; + cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &type_spec_flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + return CopyValue(env, cookie, value, ref, type_spec_flags, nullptr, typed_value); } -static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - String16 str; - if (name.package != NULL) { - str.setTo(name.package, name.packageLen); - } - if (name.type8 != NULL || name.type != NULL) { - if (str.size() > 0) { - char16_t div = ':'; - str.append(&div, 1); - } - if (name.type8 != NULL) { - str.append(String16(name.type8, name.typeLen)); - } else { - str.append(name.type, name.typeLen); - } - } - if (name.name8 != NULL || name.name != NULL) { - if (str.size() > 0) { - char16_t div = '/'; - str.append(&div, 1); - } - if (name.name8 != NULL) { - str.append(String16(name.name8, name.nameLen)); - } else { - str.append(name.name, name.nameLen); - } - } - - return env->NewString((const jchar*)str.string(), str.size()); +static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count); + if (env->ExceptionCheck()) { + return nullptr; + } + + for (uint32_t i = 0; i < bag->entry_count; i++) { + jint attr_resid = bag->entries[i].key; + env->SetIntArrayRegion(array, i, 1, &attr_resid); + } + return array; } -static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - if (name.package != NULL) { - return env->NewString((const jchar*)name.package, name.packageLen); - } - - return NULL; +static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jobjectArray array = env->NewObjectArray(bag->entry_count, g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } + + for (uint32_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + + // Resolve any references to their final value. + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return nullptr; + } + + if (value.dataType == Res_value::TYPE_STRING) { + const ApkAssets* apk_assets = assetmanager->GetApkAssets()[cookie]; + const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool(); + + jstring java_string = nullptr; + size_t str_len; + const char* str_utf8 = pool->string8At(value.data, &str_len); + if (str_utf8 != nullptr) { + java_string = env->NewStringUTF(str_utf8); + } else { + const char16_t* str_utf16 = pool->stringAt(value.data, &str_len); + java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16), str_len); + } + + // Check for errors creating the strings (if malformed or no memory). + if (env->ExceptionCheck()) { + return nullptr; + } + + env->SetObjectArrayElement(array, i, java_string); + + // If we have a large amount of string in our array, we might overflow the + // local reference table of the VM. + env->DeleteLocalRef(java_string); + } + } + return array; } -static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - if (name.type8 != NULL) { - return env->NewStringUTF(name.type8); - } - - if (name.type != NULL) { - return env->NewString((const jchar*)name.type, name.typeLen); - } +static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count * 2); + if (array == nullptr) { + return nullptr; + } + + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr)); + if (buffer == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT); + return nullptr; + } + + jint string_index = -1; + if (value.dataType == Res_value::TYPE_STRING) { + string_index = static_cast<jint>(value.data); + } + + buffer[i * 2] = ApkAssetsCookieToJavaCookie(cookie); + buffer[(i * 2) + 1] = string_index; + } + env->ReleasePrimitiveArrayCritical(array, buffer, 0); + return array; +} - return NULL; +static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count); + if (array == nullptr) { + return nullptr; + } + + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr)); + if (buffer == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT); + return nullptr; + } + + if (value.dataType >= Res_value::TYPE_FIRST_INT && value.dataType <= Res_value::TYPE_LAST_INT) { + buffer[i] = static_cast<jint>(value.data); + } + } + env->ReleasePrimitiveArrayCritical(array, buffer, 0); + return array; } -static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } +static jint NativeGetResourceArraySize(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return -1; + } + return static_cast<jint>(bag->entry_count); +} - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } +static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jintArray out_data) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return -1; + } - if (name.name8 != NULL) { - return env->NewStringUTF(name.name8); - } + const jsize out_data_length = env->GetArrayLength(out_data); + if (env->ExceptionCheck()) { + return -1; + } - if (name.name != NULL) { - return env->NewString((const jchar*)name.name, name.nameLen); - } + if (static_cast<jsize>(bag->entry_count) > out_data_length * STYLE_NUM_ENTRIES) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Input array is not large enough"); + return -1; + } - return NULL; + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_data, nullptr)); + if (buffer == nullptr) { + return -1; + } + + jint* cursor = buffer; + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + selected_config.density = 0; + uint32_t flags = bag->type_spec_flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(out_data, buffer, JNI_ABORT); + return -1; + } + + // Deal with the special @null value -- it turns back to TYPE_NULL. + if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { + value.dataType = Res_value::TYPE_NULL; + value.data = Res_value::DATA_NULL_UNDEFINED; + } + + cursor[STYLE_TYPE] = static_cast<jint>(value.dataType); + cursor[STYLE_DATA] = static_cast<jint>(value.data); + cursor[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); + cursor[STYLE_RESOURCE_ID] = static_cast<jint>(ref); + cursor[STYLE_CHANGING_CONFIGURATIONS] = static_cast<jint>(flags); + cursor[STYLE_DENSITY] = static_cast<jint>(selected_config.density); + cursor += STYLE_NUM_ENTRIES; + } + env->ReleasePrimitiveArrayCritical(out_data, buffer, 0); + return static_cast<jint>(bag->entry_count); } -static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, - jint ident, - jshort density, - jobject outValue, - jboolean resolve) -{ - if (outValue == NULL) { - jniThrowNullPointerException(env, "outValue"); - return 0; - } - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - Res_value value; - ResTable_config config; - uint32_t typeSpecFlags; - ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - uint32_t ref = ident; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - } - if (block >= 0) { - return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); - } - - return static_cast<jint>(block); +static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name, + jstring def_type, jstring def_package) { + ScopedUtfChars name_utf8(env, name); + if (name_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + std::string type; + if (def_type != nullptr) { + ScopedUtfChars type_utf8(env, def_type); + CHECK(type_utf8.c_str() != nullptr); + type = type_utf8.c_str(); + } + + std::string package; + if (def_package != nullptr) { + ScopedUtfChars package_utf8(env, def_package); + CHECK(package_utf8.c_str() != nullptr); + package = package_utf8.c_str(); + } + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return static_cast<jint>(assetmanager->GetResourceId(name_utf8.c_str(), type, package)); } -static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz, - jint ident, jint bagEntryId, - jobject outValue, jboolean resolve) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - - ssize_t block = -1; - Res_value value; +static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } - const ResTable::bag_entry* entry = NULL; - uint32_t typeSpecFlags; - ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags); + std::string result; + if (name.package != nullptr) { + result.append(name.package, name.package_len); + } - for (ssize_t i=0; i<entryCount; i++) { - if (((uint32_t)bagEntryId) == entry->map.name.ident) { - block = entry->stringBlock; - value = entry->map.value; - } - entry++; + if (name.type != nullptr || name.type16 != nullptr) { + if (!result.empty()) { + result += ":"; } - res.unlock(); - - if (block < 0) { - return static_cast<jint>(block); + if (name.type != nullptr) { + result.append(name.type, name.type_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len)); } + } - uint32_t ref = ident; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - } - if (block >= 0) { - return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags); + if (name.entry != nullptr || name.entry16 != nullptr) { + if (!result.empty()) { + result += "/"; } - return static_cast<jint>(block); -} - -static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; + if (name.entry != nullptr) { + result.append(name.entry, name.entry_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len)); } - return am->getResources().getTableCount(); + } + return env->NewStringUTF(result.c_str()); } -static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz, - jint block) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block)); +static jstring NativeGetResourcePackageName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } + + if (name.package != nullptr) { + return env->NewStringUTF(name.package); + } + return nullptr; } -static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz, - jint cookie) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - String8 name(am->getAssetPath(static_cast<int32_t>(cookie))); - if (name.length() == 0) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name"); - return NULL; - } - jstring str = env->NewStringUTF(name.string()); - return str; +static jstring NativeGetResourceTypeName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } + + if (name.type != nullptr) { + return env->NewStringUTF(name.type); + } else if (name.type16 != nullptr) { + return env->NewString(reinterpret_cast<const jchar*>(name.type16), name.type_len); + } + return nullptr; } -static jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - const ResTable& res = am->getResources(); - - jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject, - gSparseArrayOffsets.constructor); - const size_t N = res.getBasePackageCount(); - for (size_t i = 0; i < N; i++) { - const String16 name = res.getBasePackageName(i); - env->CallVoidMethod( - sparseArray, gSparseArrayOffsets.put, - static_cast<jint>(res.getBasePackageId(i)), - env->NewString(reinterpret_cast<const jchar*>(name.string()), - name.size())); - } - return sparseArray; +static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } + + if (name.entry != nullptr) { + return env->NewStringUTF(name.entry); + } else if (name.entry16 != nullptr) { + return env->NewString(reinterpret_cast<const jchar*>(name.entry16), name.entry_len); + } + return nullptr; } -static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - return reinterpret_cast<jlong>(new ResTable::Theme(am->getResources())); +static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr, + jboolean exclude_system) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::set<std::string> locales = + assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/); + + jobjectArray array = env->NewObjectArray(locales.size(), g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } + + size_t idx = 0; + for (const std::string& locale : locales) { + jstring java_string = env->NewStringUTF(locale.c_str()); + if (java_string == nullptr) { + return nullptr; + } + env->SetObjectArrayElement(array, idx++, java_string); + env->DeleteLocalRef(java_string); + } + return array; } -static void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz, - jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - delete theme; +static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config) { + jobject result = + env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor); + if (result == nullptr) { + return nullptr; + } + + env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset, + config.smallestScreenWidthDp); + env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); + env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); + return result; } -static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz, - jlong themeHandle, - jint styleRes, - jboolean force) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - theme->applyStyle(styleRes, force ? true : false); -} - -static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz, - jlong destHandle, jlong srcHandle) -{ - ResTable::Theme* dest = reinterpret_cast<ResTable::Theme*>(destHandle); - ResTable::Theme* src = reinterpret_cast<ResTable::Theme*>(srcHandle); - dest->setTo(*src); -} +static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::set<ResTable_config> configurations = + assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/); -static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - theme->clear(); -} + jobjectArray array = + env->NewObjectArray(configurations.size(), gConfigurationOffsets.classObject, nullptr); + if (array == nullptr) { + return nullptr; + } -static jint android_content_AssetManager_loadThemeAttributeValue( - JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - const ResTable& res(theme->getResTable()); - - Res_value value; - // XXX value could be different in different configs! - uint32_t typeSpecFlags = 0; - ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags); - uint32_t ref = 0; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } + size_t idx = 0; + for (const ResTable_config& configuration : configurations) { + jobject java_configuration = ConstructConfigurationObject(env, configuration); + if (java_configuration == nullptr) { + return nullptr; } - return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block; -} -static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz, - jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - return theme->getChangingConfigurations(); + env->SetObjectArrayElement(array, idx++, java_configuration); + env->DeleteLocalRef(java_configuration); + } + return array; } -static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, - jlong themeHandle, jint pri, - jstring tag, jstring prefix) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - const ResTable& res(theme->getResTable()); - (void)res; - - // XXX Need to use params. - theme->dumpToLog(); +static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr, + jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); + uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr); + uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr); + + jsize attrs_len = env->GetArrayLength(java_attrs); + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return; + } + + ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr), + static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len, + out_values, out_indices); + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); } -static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz, - jlong themeToken, - jint defStyleAttr, - jint defStyleRes, - jintArray inValues, - jintArray attrs, - jintArray outValues, - jintArray outIndices) -{ - if (themeToken == 0) { - jniThrowNullPointerException(env, "theme token"); - return JNI_FALSE; - } - if (attrs == NULL) { - jniThrowNullPointerException(env, "attrs"); - return JNI_FALSE; - } - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - const jsize NI = env->GetArrayLength(attrs); - const jsize NV = env->GetArrayLength(outValues); - if (NV < (NI*STYLE_NUM_ENTRIES)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); +static jboolean NativeResolveAttrs(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint def_style_attr, jint def_style_resid, jintArray java_values, + jintArray java_attrs, jintArray out_java_values, + jintArray out_java_indices) { + const jsize attrs_len = env->GetArrayLength(java_attrs); + const jsize out_values_len = env->GetArrayLength(out_java_values); + if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small"); + return JNI_FALSE; + } + + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return JNI_FALSE; + } + + jint* values = nullptr; + jsize values_len = 0; + if (java_values != nullptr) { + values_len = env->GetArrayLength(java_values); + values = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_values, nullptr)); + if (values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return JNI_FALSE; + } + } + + jint* out_values = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr)); + if (out_values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + return JNI_FALSE; + } + + jint* out_indices = nullptr; + if (out_java_indices != nullptr) { + jsize out_indices_len = env->GetArrayLength(out_java_indices); + if (out_indices_len > attrs_len) { + out_indices = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr)); + if (out_indices == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT); return JNI_FALSE; - } + } + } + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + bool result = ResolveAttrs( + theme, static_cast<uint32_t>(def_style_attr), static_cast<uint32_t>(def_style_resid), + reinterpret_cast<uint32_t*>(values), values_len, reinterpret_cast<uint32_t*>(attrs), + attrs_len, reinterpret_cast<uint32_t*>(out_values), reinterpret_cast<uint32_t*>(out_indices)); + if (out_indices != nullptr) { + env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0); + } + + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return result ? JNI_TRUE : JNI_FALSE; +} - jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); - if (src == NULL) { +static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jlong xml_parser_ptr, jintArray java_attrs, + jintArray out_java_values, jintArray out_java_indices) { + const jsize attrs_len = env->GetArrayLength(java_attrs); + const jsize out_values_len = env->GetArrayLength(out_java_values); + if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small"); + return JNI_FALSE; + } + + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return JNI_FALSE; + } + + jint* out_values = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr)); + if (out_values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return JNI_FALSE; + } + + jint* out_indices = nullptr; + if (out_java_indices != nullptr) { + jsize out_indices_len = env->GetArrayLength(out_java_indices); + if (out_indices_len > attrs_len) { + out_indices = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr)); + if (out_indices == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT); return JNI_FALSE; + } } + } - jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0); - const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues); + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - if (baseDest == NULL) { - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return JNI_FALSE; - } - - jint* indices = NULL; - if (outIndices != NULL) { - if (env->GetArrayLength(outIndices) > NI) { - indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); - } - } + bool result = RetrieveAttributes(assetmanager.get(), xml_parser, + reinterpret_cast<uint32_t*>(attrs), attrs_len, + reinterpret_cast<uint32_t*>(out_values), + reinterpret_cast<uint32_t*>(out_indices)); - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); - bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes, - (uint32_t*) srcValues, NSV, - (uint32_t*) src, NI, - (uint32_t*) baseDest, - (uint32_t*) indices); - - if (indices != NULL) { - env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); - } - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0); - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return result ? JNI_TRUE : JNI_FALSE; + if (out_indices != nullptr) { + env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0); + } + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0); + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return result ? JNI_TRUE : JNI_FALSE; } -static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken, - jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length, - jlong outValuesAddress, jlong outIndicesAddress) { - jint* attrs = env->GetIntArrayElements(attrsObj, 0); - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); - ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken); - uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress)); - uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress)); - ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes, - reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices); - env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT); +static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return reinterpret_cast<jlong>(assetmanager->NewTheme().release()); } -static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz, - jlong xmlParserToken, - jintArray attrs, - jintArray outValues, - jintArray outIndices) -{ - if (xmlParserToken == 0) { - jniThrowNullPointerException(env, "xmlParserToken"); - return JNI_FALSE; - } - if (attrs == NULL) { - jniThrowNullPointerException(env, "attrs"); - return JNI_FALSE; - } - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_FALSE; - } - const ResTable& res(am->getResources()); - ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken; - - const jsize NI = env->GetArrayLength(attrs); - const jsize NV = env->GetArrayLength(outValues); - if (NV < (NI*STYLE_NUM_ENTRIES)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); - return JNI_FALSE; - } - - jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); - if (src == NULL) { - return JNI_FALSE; - } - - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - if (baseDest == NULL) { - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return JNI_FALSE; - } - - jint* indices = NULL; - if (outIndices != NULL) { - if (env->GetArrayLength(outIndices) > NI) { - indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); - } - } - - bool result = RetrieveAttributes(&res, xmlParser, - (uint32_t*) src, NI, - (uint32_t*) baseDest, - (uint32_t*) indices); - - if (indices != NULL) { - env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); - } - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return result ? JNI_TRUE : JNI_FALSE; +static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) { + delete reinterpret_cast<Theme*>(theme_ptr); } -static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz, - jint id) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - res.lock(); - const ResTable::bag_entry* defStyleEnt = NULL; - ssize_t bagOff = res.getBagLocked(id, &defStyleEnt); - res.unlock(); - - return static_cast<jint>(bagOff); +static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint resid, jboolean force) { + // AssetManager is accessed via the theme, so grab an explicit lock here. + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + theme->ApplyStyle(static_cast<uint32_t>(resid), force); + + // TODO(adamlesinski): Consider surfacing exception when result is failure. + // CTS currently expects no exceptions from this method. + // std::string error_msg = StringPrintf("Failed to apply style 0x%08x to theme", resid); + // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); } -static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz, - jint id, - jintArray outValues) -{ - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_FALSE; - } - const ResTable& res(am->getResources()); - ResTable_config config; - Res_value value; - ssize_t block; - - const jsize NV = env->GetArrayLength(outValues); - - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - jint* dest = baseDest; - if (dest == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", ""); - return JNI_FALSE; - } - - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - - const ResTable::bag_entry* arrayEnt = NULL; - uint32_t arrayTypeSetFlags = 0; - ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags); - const ResTable::bag_entry* endArrayEnt = arrayEnt + - (bagOff >= 0 ? bagOff : 0); - - int i = 0; - uint32_t typeSetFlags; - while (i < NV && arrayEnt < endArrayEnt) { - block = arrayEnt->stringBlock; - typeSetFlags = arrayTypeSetFlags; - config.density = 0; - value = arrayEnt->map.value; - - uint32_t resid = 0; - if (value.dataType != Res_value::TYPE_NULL) { - // Take care of resolving the found resource to its final value. - //printf("Resolving attribute reference\n"); - ssize_t newBlock = res.resolveReference(&value, block, &resid, - &typeSetFlags, &config); - if (kThrowOnBadId) { - if (newBlock == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return JNI_FALSE; - } - } - if (newBlock >= 0) block = newBlock; - } - - // Deal with the special @null value -- it turns back to TYPE_NULL. - if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - value.dataType = Res_value::TYPE_NULL; - value.data = Res_value::DATA_NULL_UNDEFINED; - } - - //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); - - // Write the final value back to Java. - dest[STYLE_TYPE] = value.dataType; - dest[STYLE_DATA] = value.data; - dest[STYLE_ASSET_COOKIE] = reinterpret_cast<jint>(res.getTableCookie(block)); - dest[STYLE_RESOURCE_ID] = resid; - dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; - dest[STYLE_DENSITY] = config.density; - dest += STYLE_NUM_ENTRIES; - i+= STYLE_NUM_ENTRIES; - arrayEnt++; - } - - i /= STYLE_NUM_ENTRIES; - - res.unlock(); - - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - - return i; +static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr, + jlong src_theme_ptr) { + Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr); + Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr); + if (!dst_theme->SetTo(*src_theme)) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Themes are from different AssetManagers"); + } } -static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return 0; - } - - int32_t assetCookie = static_cast<int32_t>(cookie); - Asset* a = assetCookie - ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return 0; - } - - const DynamicRefTable* dynamicRefTable = - am->getResources().getDynamicRefTableForCookie(assetCookie); - ResXMLTree* block = new ResXMLTree(dynamicRefTable); - status_t err = block->setTo(a->getBuffer(true), a->getLength(), true); - a->close(); - delete a; - - if (err != NO_ERROR) { - jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); - return 0; - } - - return reinterpret_cast<jlong>(block); +static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) { + reinterpret_cast<Theme*>(theme_ptr)->Clear(); } -static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N * 2); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) { - jint stringIndex = -1; - jint stringBlock = 0; - value = bag->map.value; - - // Take care of resolving the found resource to its final value. - stringBlock = res.resolveReference(&value, bag->stringBlock, NULL); - if (value.dataType == Res_value::TYPE_STRING) { - stringIndex = value.data; - } - - if (kThrowOnBadId) { - if (stringBlock == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - - //todo: It might be faster to allocate a C array to contain - // the blocknums and indices, put them in there and then - // do just one SetIntArrayRegion() - env->SetIntArrayRegion(array, j, 1, &stringBlock); - env->SetIntArrayRegion(array, j + 1, 1, &stringIndex); - j = j + 2; - } - res.unlockBag(startOfBag); - return array; +static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint resid, jobject typed_value, + jboolean resolve_references) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + Res_value value; + uint32_t flags; + ApkAssetsCookie cookie = theme->GetAttribute(static_cast<uint32_t>(resid), &value, &flags); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t ref = 0u; + if (resolve_references) { + ResTable_config selected_config; + cookie = + theme->GetAssetManager()->ResolveReference(cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + } + return CopyValue(env, cookie, value, ref, flags, nullptr, typed_value); } -static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL); - if (env->ExceptionCheck()) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - size_t strLen = 0; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - value = bag->map.value; - jstring str = NULL; - - // Take care of resolving the found resource to its final value. - ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - if (value.dataType == Res_value::TYPE_STRING) { - const ResStringPool* pool = res.getTableStringBlock(block); - const char* str8 = pool->string8At(value.data, &strLen); - if (str8 != NULL) { - str = env->NewStringUTF(str8); - } else { - const char16_t* str16 = pool->stringAt(value.data, &strLen); - str = env->NewString(reinterpret_cast<const jchar*>(str16), - strLen); - } - - // If one of our NewString{UTF} calls failed due to memory, an - // exception will be pending. - if (env->ExceptionCheck()) { - res.unlockBag(startOfBag); - return NULL; - } - - env->SetObjectArrayElement(array, i, str); - - // str is not NULL at that point, otherwise ExceptionCheck would have been true. - // If we have a large amount of strings in our array, we might - // overflow the local reference table of the VM. - env->DeleteLocalRef(str); - } - } - res.unlockBag(startOfBag); - return array; +static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint priority, jstring tag, jstring prefix) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + (void) theme; + (void) priority; + (void) tag; + (void) prefix; } -static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - value = bag->map.value; - - // Take care of resolving the found resource to its final value. - ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - if (value.dataType >= Res_value::TYPE_FIRST_INT - && value.dataType <= Res_value::TYPE_LAST_INT) { - int intVal = value.data; - env->SetIntArrayRegion(array, i, 1, &intVal); - } - } - res.unlockBag(startOfBag); - return array; +static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/, + jlong theme_ptr) { + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + return static_cast<jint>(theme->GetChangingConfigurations()); } -static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz, - jint styleId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(styleId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } +static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + delete reinterpret_cast<Asset*>(asset_ptr); +} - const ResTable::bag_entry* bag = startOfBag; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - int resourceId = bag->map.name.ident; - env->SetIntArrayRegion(array, i, 1, &resourceId); - } - res.unlockBag(startOfBag); - return array; +static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + uint8_t b; + ssize_t res = asset->read(&b, sizeof(b)); + return res == sizeof(b) ? static_cast<jint>(b) : -1; } -static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) -{ - if (isSystem) { - verifySystemIdmaps(); - } - AssetManager* am = new AssetManager(); - if (am == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", ""); - return; - } +static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer, + jint offset, jint len) { + if (len == 0) { + return 0; + } - am->addDefaultAssets(); + jsize buffer_len = env->GetArrayLength(java_buffer); + if (offset < 0 || offset >= buffer_len || len < 0 || len > buffer_len || + offset > buffer_len - len) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", ""); + return -1; + } - ALOGV("Created AssetManager %p for Java object %p\n", am, clazz); - env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am)); -} + ScopedByteArrayRW byte_array(env, java_buffer); + if (byte_array.get() == nullptr) { + return -1; + } -static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz) -{ - AssetManager* am = (AssetManager*) - (env->GetLongField(clazz, gAssetManagerOffsets.mObject)); - ALOGV("Destroying AssetManager %p for Java object %p\n", am, clazz); - if (am != NULL) { - delete am; - env->SetLongField(clazz, gAssetManagerOffsets.mObject, 0); - } + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + ssize_t res = asset->read(byte_array.get() + offset, len); + if (res < 0) { + jniThrowException(env, "java/io/IOException", ""); + return -1; + } + return res > 0 ? static_cast<jint>(res) : -1; } -static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz) -{ - return Asset::getGlobalCount(); +static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset, + jint whence) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->seek( + static_cast<off64_t>(offset), (whence > 0 ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR)))); } -static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, jobject clazz) -{ - String8 alloc = Asset::getAssetAllocations(); - if (alloc.length() <= 0) { - return NULL; - } - - jstring str = env->NewStringUTF(alloc.string()); - return str; +static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->getLength()); } -static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz) -{ - return AssetManager::getGlobalCount(); +static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->getRemainingLength()); } // ---------------------------------------------------------------------------- -/* - * JNI registration. - */ +// JNI registration. static const JNINativeMethod gAssetManagerMethods[] = { - /* name, signature, funcPtr */ - - // Basic asset stuff. - { "openAsset", "(Ljava/lang/String;I)J", - (void*) android_content_AssetManager_openAsset }, - { "openAssetFd", "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*) android_content_AssetManager_openAssetFd }, - { "openNonAssetNative", "(ILjava/lang/String;I)J", - (void*) android_content_AssetManager_openNonAssetNative }, - { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*) android_content_AssetManager_openNonAssetFdNative }, - { "list", "(Ljava/lang/String;)[Ljava/lang/String;", - (void*) android_content_AssetManager_list }, - { "destroyAsset", "(J)V", - (void*) android_content_AssetManager_destroyAsset }, - { "readAssetChar", "(J)I", - (void*) android_content_AssetManager_readAssetChar }, - { "readAsset", "(J[BII)I", - (void*) android_content_AssetManager_readAsset }, - { "seekAsset", "(JJI)J", - (void*) android_content_AssetManager_seekAsset }, - { "getAssetLength", "(J)J", - (void*) android_content_AssetManager_getAssetLength }, - { "getAssetRemainingLength", "(J)J", - (void*) android_content_AssetManager_getAssetRemainingLength }, - { "addAssetPathNative", "(Ljava/lang/String;Z)I", - (void*) android_content_AssetManager_addAssetPath }, - { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I", - (void*) android_content_AssetManager_addAssetFd }, - { "addOverlayPathNative", "(Ljava/lang/String;)I", - (void*) android_content_AssetManager_addOverlayPath }, - { "isUpToDate", "()Z", - (void*) android_content_AssetManager_isUpToDate }, - - // Resources. - { "getLocales", "()[Ljava/lang/String;", - (void*) android_content_AssetManager_getLocales }, - { "getNonSystemLocales", "()[Ljava/lang/String;", - (void*) android_content_AssetManager_getNonSystemLocales }, - { "getSizeConfigurations", "()[Landroid/content/res/Configuration;", - (void*) android_content_AssetManager_getSizeConfigurations }, - { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V", - (void*) android_content_AssetManager_setConfiguration }, - { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*) android_content_AssetManager_getResourceIdentifier }, - { "getResourceName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceName }, - { "getResourcePackageName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourcePackageName }, - { "getResourceTypeName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceTypeName }, - { "getResourceEntryName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceEntryName }, - { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadResourceValue }, - { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadResourceBagValue }, - { "getStringBlockCount","()I", - (void*) android_content_AssetManager_getStringBlockCount }, - { "getNativeStringBlock","(I)J", - (void*) android_content_AssetManager_getNativeStringBlock }, - { "getCookieName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getCookieName }, - { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;", - (void*) android_content_AssetManager_getAssignedPackageIdentifiers }, - - // Themes. - { "newTheme", "()J", - (void*) android_content_AssetManager_newTheme }, - { "deleteTheme", "(J)V", - (void*) android_content_AssetManager_deleteTheme }, - { "applyThemeStyle", "(JIZ)V", - (void*) android_content_AssetManager_applyThemeStyle }, - { "copyTheme", "(JJ)V", - (void*) android_content_AssetManager_copyTheme }, - { "clearTheme", "(J)V", - (void*) android_content_AssetManager_clearTheme }, - { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadThemeAttributeValue }, - { "getThemeChangingConfigurations", "(J)I", - (void*) android_content_AssetManager_getThemeChangingConfigurations }, - { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V", - (void*) android_content_AssetManager_dumpTheme }, - { "applyStyle","(JIIJ[IIJJ)V", - (void*) android_content_AssetManager_applyStyle }, - { "resolveAttrs","(JII[I[I[I[I)Z", - (void*) android_content_AssetManager_resolveAttrs }, - { "retrieveAttributes","(J[I[I[I)Z", - (void*) android_content_AssetManager_retrieveAttributes }, - { "getArraySize","(I)I", - (void*) android_content_AssetManager_getArraySize }, - { "retrieveArray","(I[I)I", - (void*) android_content_AssetManager_retrieveArray }, - - // XML files. - { "openXmlAssetNative", "(ILjava/lang/String;)J", - (void*) android_content_AssetManager_openXmlAssetNative }, - - // Arrays. - { "getArrayStringResource","(I)[Ljava/lang/String;", - (void*) android_content_AssetManager_getArrayStringResource }, - { "getArrayStringInfo","(I)[I", - (void*) android_content_AssetManager_getArrayStringInfo }, - { "getArrayIntResource","(I)[I", - (void*) android_content_AssetManager_getArrayIntResource }, - { "getStyleAttributes","(I)[I", - (void*) android_content_AssetManager_getStyleAttributes }, - - // Bookkeeping. - { "init", "(Z)V", - (void*) android_content_AssetManager_init }, - { "destroy", "()V", - (void*) android_content_AssetManager_destroy }, - { "getGlobalAssetCount", "()I", - (void*) android_content_AssetManager_getGlobalAssetCount }, - { "getAssetAllocations", "()Ljava/lang/String;", - (void*) android_content_AssetManager_getAssetAllocations }, - { "getGlobalAssetManagerCount", "()I", - (void*) android_content_AssetManager_getGlobalAssetManagerCount }, + // AssetManager setup methods. + {"nativeCreate", "()J", (void*)NativeCreate}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, + {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V", + (void*)NativeSetConfiguration}, + {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;", + (void*)NativeGetAssignedPackageIdentifiers}, + + // AssetManager file methods. + {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList}, + {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset}, + {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenAssetFd}, + {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset}, + {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenNonAssetFd}, + {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset}, + + // AssetManager resource methods. + {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue}, + {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I", + (void*)NativeGetResourceBagValue}, + {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes}, + {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;", + (void*)NativeGetResourceStringArray}, + {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo}, + {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray}, + {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize}, + {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray}, + + // AssetManager resource name/ID methods. + {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)NativeGetResourceIdentifier}, + {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName}, + {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName}, + {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName}, + {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName}, + {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales}, + {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;", + (void*)NativeGetSizeConfigurations}, + + // Style attribute related methods. + {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle}, + {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs}, + {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes}, + + // Theme related methods. + {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate}, + {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy}, + {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle}, + {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy}, + {"nativeThemeClear", "(J)V", (void*)NativeThemeClear}, + {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I", + (void*)NativeThemeGetAttributeValue}, + {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump}, + {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations}, + + // AssetInputStream methods. + {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy}, + {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar}, + {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead}, + {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek}, + {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength}, + {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength}, + + // System/idmap related methods. + {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps}, + + // Global management/debug methods. + {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount}, + {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations}, + {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount}, }; -int register_android_content_AssetManager(JNIEnv* env) -{ - jclass typedValue = FindClassOrDie(env, "android/util/TypedValue"); - gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I"); - gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I"); - gTypedValueOffsets.mString = GetFieldIDOrDie(env, typedValue, "string", - "Ljava/lang/CharSequence;"); - gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I"); - gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I"); - gTypedValueOffsets.mChangingConfigurations = GetFieldIDOrDie(env, typedValue, - "changingConfigurations", "I"); - gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); - - jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); - gAssetFileDescriptorOffsets.mFd = GetFieldIDOrDie(env, assetFd, "mFd", - "Landroid/os/ParcelFileDescriptor;"); - gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); - gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); - - jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); - gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); - - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - g_stringClass = MakeGlobalRefOrDie(env, stringClass); - - jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray"); - gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass); - gSparseArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, - "<init>", "()V"); - gSparseArrayOffsets.put = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", - "(ILjava/lang/Object;)V"); - - jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration"); - gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass); - gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, - "<init>", "()V"); - gConfigurationOffsets.mSmallestScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass, - "smallestScreenWidthDp", "I"); - gConfigurationOffsets.mScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass, - "screenWidthDp", "I"); - gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass, - "screenHeightDp", "I"); - - return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods, - NELEM(gAssetManagerMethods)); +int register_android_content_AssetManager(JNIEnv* env) { + jclass apk_assets_class = FindClassOrDie(env, "android/content/res/ApkAssets"); + gApkAssetsFields.native_ptr = GetFieldIDOrDie(env, apk_assets_class, "mNativePtr", "J"); + + jclass typedValue = FindClassOrDie(env, "android/util/TypedValue"); + gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I"); + gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I"); + gTypedValueOffsets.mString = + GetFieldIDOrDie(env, typedValue, "string", "Ljava/lang/CharSequence;"); + gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I"); + gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I"); + gTypedValueOffsets.mChangingConfigurations = + GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I"); + gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); + + jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); + gAssetFileDescriptorOffsets.mFd = + GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;"); + gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); + gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); + + jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); + gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); + + jclass stringClass = FindClassOrDie(env, "java/lang/String"); + g_stringClass = MakeGlobalRefOrDie(env, stringClass); + + jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray"); + gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass); + gSparseArrayOffsets.constructor = + GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "<init>", "()V"); + gSparseArrayOffsets.put = + GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V"); + + jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration"); + gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass); + gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, "<init>", "()V"); + gConfigurationOffsets.mSmallestScreenWidthDpOffset = + GetFieldIDOrDie(env, configurationClass, "smallestScreenWidthDp", "I"); + gConfigurationOffsets.mScreenWidthDpOffset = + GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I"); + gConfigurationOffsets.mScreenHeightDpOffset = + GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I"); + + return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods, + NELEM(gAssetManagerMethods)); } }; // namespace android diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 1350f3f46aca..5b788a644852 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -673,7 +673,7 @@ jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) nativeData->mObject = val; gNativeDataCache = nullptr; ++gNumProxies; - if (++gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) { + if (gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) { ALOGW("Unexpectedly many live BinderProxies: %d\n", gNumProxies); gProxiesWarned = gNumProxies; } diff --git a/core/jni/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h index 8dd933707a6a..2c1e3579eb92 100644 --- a/core/jni/include/android_runtime/android_util_AssetManager.h +++ b/core/jni/include/android_runtime/android_util_AssetManager.h @@ -14,17 +14,20 @@ * limitations under the License. */ -#ifndef android_util_AssetManager_H -#define android_util_AssetManager_H +#ifndef ANDROID_RUNTIME_ASSETMANAGER_H +#define ANDROID_RUNTIME_ASSETMANAGER_H -#include <androidfw/AssetManager.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/MutexGuard.h" #include "jni.h" namespace android { -extern AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject assetMgr); +extern AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager); +extern Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager); +extern Guarded<AssetManager2>* AssetManagerForNdkAssetManager(AAssetManager* assetmanager); -} +} // namespace android -#endif +#endif // ANDROID_RUNTIME_ASSETMANAGER_H diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 0b0ed8386c60..698f394385ab 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -204,7 +204,10 @@ message IncidentProto { (section).args = "power --proto" ]; - optional android.service.print.PrintServiceDumpProto print = 3010; + optional android.service.print.PrintServiceDumpProto print = 3010 [ + (section).type = SECTION_DUMPSYS, + (section).args = "print --proto" + ]; optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [ (section).type = SECTION_DUMPSYS, diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto index 0342c9c89527..d1c5db66a841 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -104,11 +104,6 @@ message AlarmManagerServiceProto { repeated InFlightProto outstanding_deliveries = 34; - // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It - // should be either CosntantsProto.allow_while_idle_short_duration_ms or - // ConstantsProto.allow_while_idle_long_duration_ms. - optional int64 allow_while_idle_min_duration_ms = 35; - message LastAllowWhileIdleDispatch { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -121,7 +116,7 @@ message AlarmManagerServiceProto { } // Whether the short or long while-idle timeout should be used for each UID. - repeated int32 use_allow_while_idle_short_time = 42; + repeated int32 use_allow_while_idle_short_time = 35; // For each uid, this is the last time we dispatched an "allow while idle" // alarm, used to determine the earliest we can dispatch the next such alarm. diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 449e54672207..c11058a22107 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -295,6 +295,7 @@ message WindowStateProto { optional bool animating_exit = 14; repeated WindowStateProto child_windows = 15; optional .android.graphics.RectProto surface_position = 16; + optional .android.graphics.RectProto shown_position = 17; optional int32 requested_width = 18; optional int32 requested_height = 19; optional int32 view_visibility = 20; diff --git a/core/proto/android/service/print.proto b/core/proto/android/service/print.proto index c2be7f175816..f783b866c0ac 100644 --- a/core/proto/android/service/print.proto +++ b/core/proto/android/service/print.proto @@ -21,13 +21,18 @@ option java_multiple_files = true; option java_outer_classname = "PrintServiceProto"; import "frameworks/base/core/proto/android/content/component_name.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message PrintServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Each user has a separate printer state repeated PrintUserStateProto userStates = 1; } message PrintUserStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Should be 0, 10, 11, 12, etc. where 0 is the owner. optional int32 user_id = 1; @@ -51,6 +56,8 @@ message PrintUserStateProto { } message PrintSpoolerStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Is the print spooler destroyed? optional bool is_destroyed = 1; @@ -62,6 +69,8 @@ message PrintSpoolerStateProto { } message PrintSpoolerInternalStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Print jobs repeated PrintJobInfoProto print_jobs = 1; @@ -73,6 +82,8 @@ message PrintSpoolerInternalStateProto { } message PrinterCapabilitiesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Minimum margins of the printer optional MarginsProto min_margins = 1; @@ -90,6 +101,8 @@ message PrinterCapabilitiesProto { } message PrinterInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // The id of the printer optional PrinterIdProto id = 1; @@ -120,6 +133,8 @@ message PrinterInfoProto { } message PrinterDiscoverySessionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Is this session destroyed? optional bool is_destroyed = 1; @@ -140,6 +155,8 @@ message PrinterDiscoverySessionProto { } message InstalledPrintServiceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Component name of the service optional android.content.ComponentNameProto component_name = 1; @@ -154,14 +171,18 @@ message InstalledPrintServiceProto { } message PrinterIdProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Component name of the service that reported the printer optional android.content.ComponentNameProto service_name = 1; // Local id of the printer - optional string local_id = 2; + optional string local_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; } message ActivePrintServiceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Component name of the service optional android.content.ComponentNameProto component_name = 1; @@ -185,6 +206,8 @@ message ActivePrintServiceProto { } message MediaSizeProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Id of this media size optional string id = 1; @@ -199,6 +222,8 @@ message MediaSizeProto { } message ResolutionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Id of this resolution optional string id = 1; @@ -213,6 +238,8 @@ message ResolutionProto { } message MarginsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Space at the top optional int32 top_mils = 1; @@ -227,6 +254,8 @@ message MarginsProto { } message PrintAttributesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Media to use optional ResolutionProto media_size = 1; @@ -270,8 +299,10 @@ message PrintAttributesProto { } message PrintDocumentInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Name of the document to print - optional string name = 1; + optional string name = 1 [ (android.privacy).dest = DEST_EXPLICIT ]; // Number of pages in the doc optional int32 page_count = 2; @@ -284,6 +315,8 @@ message PrintDocumentInfoProto { } message PageRangeProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Start of the range optional int32 start = 1; @@ -292,8 +325,10 @@ message PageRangeProto { } message PrintJobInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Label of the job - optional string label = 1; + optional string label = 1 [ (android.privacy).dest = DEST_EXPLICIT ]; // Id of the job optional string print_job_id = 2; @@ -359,6 +394,8 @@ message PrintJobInfoProto { } message CachedPrintJobProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // The id of the app the job belongs to optional int32 app_id = 1; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f627aaa7751e..deefddbf5f73 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3861,6 +3861,7 @@ android:excludeFromRecents="true" android:label="@string/user_owner_label" android:exported="true" + android:visibleToInstantApps="true" > </activity> <activity-alias android:name="com.android.internal.app.ForwardIntentToParent" diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index a6dee8a9beb3..f5bbadcce06b 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2017 The Android Open Source Project +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. @@ -14,36 +13,21 @@ Copyright (C) 2017 The Android Open Source Project See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" - android:viewportWidth="48" - android:viewportHeight="48"> - <group> - <path - android:fillColor="#2C292A" - android:fillType="evenOdd" - android:pathData="M6,26a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/> - <path - android:fillColor="#FAFAFA" - android:fillType="evenOdd" - android:pathData="M4,24a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/> - <path - android:fillColor="#2C292A" - android:fillType="evenOdd" - android:pathData="M2,22a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/> - <path - android:fillColor="#00000000" - android:strokeColor="#453F41" - android:strokeWidth="1" - android:fillType="evenOdd" - android:pathData="M26.5 29.5v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3h-1v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3H17a1.5 1.5 0 0 1-1.5-1.5V17.5h13V28a1.5 1.5 0 0 1-1.5 1.5h-.5zM13.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM30.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM26.3 12.11A6.46 6.46 0 0 1 28.5 17v.5h-13V17a6.46 6.46 0 0 1 2.2-4.89l-.9-.9a.98.98 0 0 1 0-1.41.98.98 0 0 1 1.4 0l1.26 1.25A6.33 6.33 0 0 1 22 10.5c.87 0 1.73.2 2.54.55L25.8 9.8a.98.98 0 0 1 1.4 0 .98.98 0 0 1 0 1.4l-.9.91z"/> - <path - android:fillColor="#453F41" - android:fillType="evenOdd" - android:pathData="M20.16 14.5a.66.66 0 1 1-1.31 0c0-.36.29-.65.65-.65.36 0 .65.29.65.65zM25.16 14.5c0 .36-.3.66-.66.66a.65.65 0 1 1 .66-.66z"/> - <path - android:fillColor="#453F41" - android:pathData="M22 40.5c0.36 0 0.73-0.01 1.09-0.03l-0.18-3A15.77 15.77 0 0 1 22 37.5v3zm2.17-0.13a18.48 18.48 0 0 0 1.08-0.15l-0.53-2.96c-0.3 0.05-0.6 0.1-0.9 0.13l0.35 2.98zM26.32 40a18.37 18.37 0 0 0 1.05-0.28l-0.87-2.87a15.37 15.37 0 0 1-0.88 0.23l0.7 2.92zm2.1-0.64l-1.03-2.81a15.39 15.39 0 0 0 0.84-0.34l1.2 2.74a18.39 18.39 0 0 1-1 0.41zm1.99-0.87l-1.37-2.67a15.46 15.46 0 0 0 0.8-0.44l1.52 2.59a18.46 18.46 0 0 1-0.95 0.52zm1.89-1.11l-1.67-2.5a15.55 15.55 0 0 0 0.74-0.52l1.81 2.39a18.55 18.55 0 0 1-0.88 0.63zm1.75-1.33l-1.95-2.28a15.6 15.6 0 0 0 0.67-0.61l2.09 2.15a18.6 18.6 0 0 1-0.8 0.74zm1.6-1.55l-2.22-2.02a15.6 15.6 0 0 0 0.6-0.7l2.33 1.9a18.6 18.6 0 0 1-0.72 0.82zM37 32.82l-2.43-1.76a15.53 15.53 0 0 0 0.5-0.75l2.54 1.6c-0.2 0.31-0.4 0.61-0.61 0.9zm1.15-1.8l-2.62-1.47a15.45 15.45 0 0 0 0.42-0.8l2.7 1.3a18.45 18.45 0 0 1-0.5 0.97zm0.95-1.98l-2.77-1.14a15.38 15.38 0 0 0 0.32-0.86l2.84 0.98a18.38 18.38 0 0 1-0.39 1.02zm0.72-2.09c0.1-0.34 0.18-0.7 0.26-1.05l-2.93-0.63a15.38 15.38 0 0 1-0.22 0.88l2.89 0.8zm0.46-2.15a18.52 18.52 0 0 0 0.13-1.08l-2.99-0.28a15.52 15.52 0 0 1-0.1 0.9l2.96 0.46zm0.2-2.2a18.81 18.81 0 0 0 0-1.1l-3 0.08a16 16 0 0 1 0 0.92l3 0.1zm-0.06-2.2a18.54 18.54 0 0 0-0.12-1.07l-2.97 0.43c0.04 0.3 0.08 0.6 0.1 0.9l3-0.25zm-0.31-2.15a18.39 18.39 0 0 0-0.25-1.06l-2.9 0.78a15.39 15.39 0 0 1 0.21 0.89l2.94-0.6zm-0.57-2.12l-2.85 0.95a15.37 15.37 0 0 0-0.31-0.85l2.78-1.12a18.37 18.37 0 0 1 0.38 1.02zm-0.83-2.06l-2.71 1.29a15.44 15.44 0 0 0-0.42-0.81l2.63-1.45a18.44 18.44 0 0 1 0.5 0.97zm-1.03-1.88l-2.54 1.6a15.53 15.53 0 0 0-0.5-0.76l2.44-1.74 0.6 0.9zm-1.28-1.79l-2.33 1.88a15.6 15.6 0 0 0-0.6-0.69l2.23-2.02a18.6 18.6 0 0 1 0.7 0.83zm-1.48-1.63l-2.1 2.14a15.6 15.6 0 0 0-0.67-0.62l1.97-2.26a18.6 18.6 0 0 1 0.8 0.74zM33.24 7.3l-1.82 2.38a15.55 15.55 0 0 0-0.74-0.53l1.68-2.49c0.3 0.2 0.6 0.42 0.88 0.64zm-1.71-1.17L29.98 8.7a15.47 15.47 0 0 0-0.8-0.45l1.4-2.66a18.47 18.47 0 0 1 0.95 0.54zm-1.95-1.02l-1.23 2.74A15.4 15.4 0 0 0 27.5 7.5l1.06-2.8a18.4 18.4 0 0 1 1.01 0.4zm-2.06-0.78l-0.9 2.86a15.37 15.37 0 0 0-0.87-0.24l0.72-2.92a18.37 18.37 0 0 1 1.05 0.3zM25.38 3.8a18.47 18.47 0 0 0-1.08-0.17l-0.37 2.98c0.3 0.04 0.6 0.08 0.9 0.14l0.55-2.95zm-2.2-0.27A18.75 18.75 0 0 0 22.1 3.5l-0.02 3L23 6.53l0.19-3zM21 3.53a18.6 18.6 0 0 0-1.08 0.09l0.33 2.98a15.6 15.6 0 0 1 0.91-0.08l-0.16-3zm-2.16 0.24A18.4 18.4 0 0 0 17.76 4l0.68 2.92a15.4 15.4 0 0 1 0.9-0.18l-0.51-2.96zm-2.14 0.5l0.86 2.88a15.37 15.37 0 0 0-0.86 0.28l-1.03-2.81a18.37 18.37 0 0 1 1.03-0.35zm-2.07 0.76l1.2 2.75a15.42 15.42 0 0 0-0.83 0.4L13.63 5.5a18.42 18.42 0 0 1 0.99-0.47zM12.7 6l1.5 2.6a15.5 15.5 0 0 0-0.76 0.48l-1.66-2.5A18.5 18.5 0 0 1 12.7 6zm-1.83 1.22l1.8 2.4a15.58 15.58 0 0 0-0.7 0.57L10.01 7.9a18.58 18.58 0 0 1 0.85-0.68zM9.19 8.66l2.07 2.16a15.6 15.6 0 0 0-0.63 0.65l-2.2-2.04a18.6 18.6 0 0 1 0.76-0.77zm-1.51 1.63l2.32 1.9a15.57 15.57 0 0 0-0.56 0.72l-2.42-1.76a18.57 18.57 0 0 1 0.66-0.86zm-1.23 1.69l2.52 1.62a15.5 15.5 0 0 0-0.47 0.78l-2.61-1.47a18.5 18.5 0 0 1 0.56-0.93zm-1.08 1.9l2.7 1.32a15.41 15.41 0 0 0-0.38 0.83l-2.77-1.15a18.41 18.41 0 0 1 0.45-1zm-0.85 2.04l2.84 0.98a15.37 15.37 0 0 0-0.28 0.87L4.2 16.96c0.1-0.35 0.2-0.7 0.32-1.04zm-0.6 2.12a18.43 18.43 0 0 0-0.2 1.07l2.97 0.47c0.05-0.3 0.1-0.6 0.17-0.9l-2.93-0.64zm-0.34 2.18a18.65 18.65 0 0 0-0.07 1.09l3 0.11 0.06-0.91-2.99-0.29zm-0.08 2.2a18.7 18.7 0 0 0 0.06 1.1l3-0.25a15.7 15.7 0 0 1-0.06-0.91l-3 0.07zm0.18 2.18a18.44 18.44 0 0 0 0.18 1.07l2.95-0.6a15.44 15.44 0 0 1-0.16-0.9L3.68 24.6zm0.43 2.14l2.9-0.77a15.37 15.37 0 0 0 0.26 0.88l-2.85 0.94a18.37 18.37 0 0 1-0.3-1.05zm0.7 2.1l2.78-1.11a15.4 15.4 0 0 0 0.36 0.83l-2.71 1.27a18.4 18.4 0 0 1-0.44-1zm0.9 1.95l2.65-1.43a15.48 15.48 0 0 0 0.45 0.8l-2.55 1.57a18.48 18.48 0 0 1-0.54-0.94zm1.17 1.87l2.45-1.73a15.56 15.56 0 0 0 0.54 0.73l-2.34 1.87a18.56 18.56 0 0 1-0.65-0.87zm1.37 1.72l2.23-2a15.6 15.6 0 0 0 0.63 0.65l-2.1 2.14a18.6 18.6 0 0 1-0.76-0.79zm1.58 1.56l1.98-2.26c0.22 0.2 0.46 0.39 0.7 0.58l-1.84 2.37a18.59 18.59 0 0 1-0.84-0.7zm1.66 1.28l1.7-2.46a15.52 15.52 0 0 0 0.77 0.5l-1.56 2.56a18.52 18.52 0 0 1-0.91-0.6zm1.87 1.14l1.4-2.65a15.43 15.43 0 0 0 0.82 0.4l-1.24 2.73a18.43 18.43 0 0 1-0.98-0.48zm2 0.91l1.08-2.8a15.37 15.37 0 0 0 0.86 0.3l-0.9 2.86a18.37 18.37 0 0 1-1.04-0.36zm2.1 0.67a18.4 18.4 0 0 0 1.07 0.23l0.56-2.94a15.4 15.4 0 0 1-0.9-0.2l-0.72 2.91zm2.18 0.41a18.57 18.57 0 0 0 1.08 0.1l0.2-2.99a15.57 15.57 0 0 1-0.9-0.09l-0.38 2.98zm2.2 0.15H22v-3h-0.13l-0.03 3z"/> - </group> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="vector" + android:width="640dp" + android:height="640dp" + android:viewportWidth="64" + android:viewportHeight="64"> + <path + android:name="bg" + android:pathData="M 27 43 L 32 43 C 38.075 43 43 38.075 43 32 C 43 25.925 38.075 21 32 21 C 25.925 21 21 25.925 21 32 L 21 64" + android:strokeColor="#6823a1" + android:strokeWidth="16"/> + <path + android:name="fg" + android:pathData="M 29 43 L 32 43 C 38.075 43 43 38.075 43 32 C 43 25.925 38.075 21 32 21 C 25.925 21 21 25.925 21 32 L 21 64" + android:strokeColor="#ff0000" + android:strokeWidth="8"/> </vector> diff --git a/core/res/res/drawable-nodpi/platlogo_m.xml b/core/res/res/drawable-nodpi/platlogo_m.xml index aacf67483d72..8e43638ef221 100644 --- a/core/res/res/drawable-nodpi/platlogo_m.xml +++ b/core/res/res/drawable-nodpi/platlogo_m.xml @@ -18,23 +18,4 @@ Copyright (C) 2017 The Android Open Source Project android:height="480dp" android:viewportWidth="48.0" android:viewportHeight="48.0"> - <!--<path - android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0" - android:fillAlpha="0.066" - android:fillColor="#000000"/>--> - <path - android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0" - android:fillColor="#FFC107"/> - <path - android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z" - android:fillColor="#FE9F00"/> - <path - android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0" - android:fillColor="#FED44F"/> - <path - android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z" - android:fillColor="#FFC107"/> - <path - android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0" - android:fillColor="#FFFFFF"/> </vector> diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml index 2e2b3956dc68..0fde2cc2aba4 100644 --- a/core/res/res/drawable-nodpi/stat_sys_adb.xml +++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2017 The Android Open Source Project +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. @@ -14,24 +13,24 @@ Copyright (C) 2017 The Android Open Source Project See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <group> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="vector" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + <group android:name="stat_sys_adb"> <path - android:fillColor="#FFFFFF" - android:fillAlpha=".33" - android:fillType="evenOdd" - android:pathData="M5.71 18.29A8.99 8.99 0 0 0 22 13c0-3-1.46-5.65-3.71-7.29A8.99 8.99 0 0 0 2 11c0 3 1.46 5.65 3.71 7.29z"/> + android:name="outer" + android:pathData="M 18 30 L 24 30 C 29.523 30 34 25.523 34 20 C 34 14.477 29.523 10 24 10 C 18.477 10 14 14.477 14 20 L 14 44" + android:strokeColor="#000000" + android:strokeWidth="10"/> <path - android:fillColor="#FFFFFF" - android:fillType="evenOdd" - android:pathData="M7.25 19.18A8.5 8.5 0 0 0 19.19 7.24 9 9 0 0 1 7.24 19.19z"/> - <path - android:fillColor="#FFFFFF" - android:fillAlpha=".33" - android:pathData="M10.5 3a0.5 0.5 0 1 1 1 0v2.05a0.5 0.5 0 1 1-1 0V3zm3.1 0.42a0.5 0.5 0 0 1 0.93 0.39l-0.8 1.88A0.5 0.5 0 1 1 12.8 5.3l0.8-1.88zm2.7 1.57a0.5 0.5 0 1 1 0.71 0.7l-1.45 1.46a0.5 0.5 0 0 1-0.7-0.71l1.44-1.45zm1.9 2.5a0.5 0.5 0 0 1 0.38 0.92l-1.9 0.77a0.5 0.5 0 0 1-0.37-0.93l1.9-0.77zM19 10.5a0.5 0.5 0 1 1 0 1h-2.05a0.5 0.5 0 0 1 0-1H19zm-0.42 3.1a0.5 0.5 0 0 1-0.39 0.93l-1.88-0.8a0.5 0.5 0 1 1 0.39-0.92l1.88 0.8zm-1.57 2.7a0.5 0.5 0 1 1-0.7 0.71l-1.46-1.45a0.5 0.5 0 0 1 0.71-0.7l1.45 1.44zm-2.5 1.9a0.5 0.5 0 1 1-0.92 0.38l-0.77-1.9a0.5 0.5 0 0 1 0.93-0.37l0.77 1.9zM11.5 19a0.5 0.5 0 1 1-1 0v-2.05a0.5 0.5 0 0 1 1 0V19zm-3.1-0.42a0.5 0.5 0 0 1-0.93-0.39l0.8-1.88A0.5 0.5 0 0 1 9.2 16.7l-0.8 1.88zm-2.7-1.57a0.5 0.5 0 1 1-0.71-0.7l1.45-1.46a0.5 0.5 0 0 1 0.7 0.71L5.7 17.01zm-1.9-2.48a0.5 0.5 0 0 1-0.38-0.92l1.88-0.8a0.5 0.5 0 0 1 0.4 0.92l-1.9 0.8zM3 11.5a0.5 0.5 0 1 1 0-1h2.05a0.5 0.5 0 1 1 0 1H3zm0.42-3.1A0.5 0.5 0 0 1 3.8 7.46l1.88 0.8A0.5 0.5 0 1 1 5.3 9.2L3.42 8.4zm1.57-2.7a0.5 0.5 0 1 1 0.7-0.71l1.46 1.45a0.5 0.5 0 0 1-0.71 0.7L4.99 5.7zm2.5-1.9A0.5 0.5 0 0 1 8.4 3.41l0.77 1.9a0.5 0.5 0 0 1-0.93 0.37L7.48 3.8z"/> + android:name="inner" + android:pathData="M 19 30 L 24 30 C 29.523 30 34 25.523 34 20 C 34 14.477 29.523 10 24 10 C 18.477 10 14 14.477 14 20 L 14 44" + android:strokeColor="#000000" + android:strokeAlpha="0" + android:strokeWidth="6"/> </group> -</vector>
\ No newline at end of file +</vector> diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 4ac308a60d93..bd1030ee14f5 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -28,9 +28,9 @@ android:scaleType="centerCrop" android:importantForAccessibility="no" /> <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/message_group_and_sender_container" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_weight="1" android:orientation="vertical"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/message_name" @@ -44,4 +44,10 @@ android:spacing="2dp" android:layout_weight="1"/> </com.android.internal.widget.RemeasuringLinearLayout> + <FrameLayout + android:id="@+id/messaging_group_icon_container" + android:layout_width="@dimen/messaging_avatar_size" + android:layout_height="@dimen/messaging_avatar_size" + android:layout_marginStart="12dp" + android:visibility="gone"/> </com.android.internal.widget.MessagingGroup> diff --git a/core/res/res/layout/notification_template_messaging_image_message.xml b/core/res/res/layout/notification_template_messaging_image_message.xml new file mode 100644 index 000000000000..6ca4dcdd7c9b --- /dev/null +++ b/core/res/res/layout/notification_template_messaging_image_message.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<com.android.internal.widget.MessagingImageMessage + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/message_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/messaging_image_extra_spacing" + android:scaleType="fitStart" +/> diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_text_message.xml index ab6466cc7b3c..e728e6912dd4 100644 --- a/core/res/res/layout/notification_template_messaging_message.xml +++ b/core/res/res/layout/notification_template_messaging_text_message.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.internal.widget.MessagingMessage +<com.android.internal.widget.MessagingTextMessage xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/message_text" style="@style/Widget.Material.Notification.MessagingText" diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index e610efd4ea6a..0411c6ed8833 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -262,6 +262,18 @@ <!-- The spacing between messages in Notification.MessagingStyle --> <dimen name="notification_messaging_spacing">6dp</dimen> + <!-- The rounding for messaging images --> + <dimen name="messaging_image_rounding">4dp</dimen> + + <!-- The minimum size for any image in messaging style in order to be displayed --> + <dimen name="messaging_image_min_size">44dp</dimen> + + <!-- The maximum size for any image in messaging style in order to be displayed --> + <dimen name="messaging_image_max_height">136dp</dimen> + + <!-- Extra spacing before and after images in messaging style --> + <dimen name="messaging_image_extra_spacing">8dp</dimen> + <!-- Preferred width and height of the search view. --> <dimen name="search_view_preferred_width">320dip</dimen> <dimen name="search_view_preferred_height">48dip</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 29b09b574318..f7b6f06a6806 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3168,7 +3168,8 @@ <java-symbol type="dimen" name="chooser_service_spacing" /> <java-symbol type="bool" name="config_showSysuiShutdown" /> - <java-symbol type="layout" name="notification_template_messaging_message" /> + <java-symbol type="layout" name="notification_template_messaging_text_message" /> + <java-symbol type="layout" name="notification_template_messaging_image_message" /> <java-symbol type="layout" name="notification_template_messaging_group" /> <java-symbol type="id" name="message_text" /> <java-symbol type="id" name="message_name" /> @@ -3183,6 +3184,11 @@ <java-symbol type="id" name="clip_children_tag" /> <java-symbol type="drawable" name="ic_reply_notification_large" /> <java-symbol type="dimen" name="messaging_avatar_size" /> + <java-symbol type="dimen" name="messaging_image_rounding" /> + <java-symbol type="dimen" name="messaging_image_min_size" /> + <java-symbol type="dimen" name="messaging_image_max_height" /> + <java-symbol type="dimen" name="messaging_image_extra_spacing" /> + <java-symbol type="id" name="messaging_group_icon_container" /> <java-symbol type="integer" name="config_stableDeviceDisplayWidth" /> <java-symbol type="integer" name="config_stableDeviceDisplayHeight" /> diff --git a/core/res/res/xml/color_extraction.xml b/core/res/res/xml/color_extraction.xml index 7d52b20f7614..93ab0ff26754 100644 --- a/core/res/res/xml/color_extraction.xml +++ b/core/res/res/xml/color_extraction.xml @@ -257,52 +257,58 @@ <!-- Red Orange --> <range h="20, 40" s="0.7, 1" - l="0.28, 0.643"/> + l="0.2, 0.643"/> <range h="20, 40" s="0.3, 0.7" l="0.414, 0.561"/> <range h="20, 40" - s="0, 3" + s="0, 0.3" l="0.343, 0.584"/> <!-- Orange --> <range h="40, 60" s="0.7, 1" - l="0.173, 0.349"/> + l="0.173, 0.38"/> <range h="40, 60" s="0.3, 0.7" l="0.233, 0.427"/> <range h="40, 60" s="0, 0.3" - l="0.231, 0.484"/> + l="0.231, 0.48"/> <!-- Yellow 60 --> <range h="60, 80" s="0.7, 1" - l="0.488, 0.737"/> + l="0.15, 0.40"/> <range h="60, 80" s="0.3, 0.7" - l="0.673, 0.837"/> + l="0.15, 0.42"/> + <range h="60, 80" + s="0, 0.3" + l="0.35, 0.57"/> <!-- Yellow Green 80 --> <range h="80, 100" s="0.7, 1" - l="0.469, 0.61"/> + l="0.36, 0.65"/> + <range h="80, 100" + s="0.3, 0.7" + l="0.48, 0.57"/> <!-- Yellow green 100 --> <range h="100, 120" s="0.7, 1" - l="0.388, 0.612"/> + l="0.388, 0.67"/> <range h="100, 120" s="0.3, 0.7" - l="0.424, 0.541"/> + l="0.424, 0.58"/> <!-- Green --> <range h="120, 140" s="0.7, 1" - l="0.375, 0.52"/> + l="0.37, 0.65"/> <range h="120, 140" s="0.3, 0.7" - l="0.435, 0.524"/> + l="0.435, 0.58"/> <!-- Green Blue 140 --> <range h="140, 160" s="0.7, 1" - l="0.496, 0.641"/> + l="0.43, 0.641"/> <!-- Seaoam --> <range h="160, 180" s="0.7, 1" diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml index dca16564aa11..d5be87dbae6b 100644 --- a/core/tests/coretests/res/layout/activity_text_view.xml +++ b/core/tests/coretests/res/layout/activity_text_view.xml @@ -24,6 +24,9 @@ android:id="@+id/textview" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <Space + android:layout_width="1dp" + android:layout_height="60dp"/> <TextView android:id="@+id/nonselectable_textview" diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 3e62183b01cd..853a36d29745 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -109,6 +109,8 @@ public class SettingsBackupTest { Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, Settings.Global.ANIMATOR_DURATION_SCALE, Settings.Global.ANOMALY_DETECTION_CONSTANTS, + Settings.Global.ANOMALY_CONFIG, + Settings.Global.ANOMALY_CONFIG_VERSION, Settings.Global.APN_DB_UPDATE_CONTENT_URL, Settings.Global.APN_DB_UPDATE_METADATA_URL, Settings.Global.APP_IDLE_CONSTANTS, @@ -194,6 +196,7 @@ public class SettingsBackupTest { Settings.Global.DEVICE_PROVISIONED, Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD, + Settings.Global.DISPLAY_PANEL_LPM, Settings.Global.DISPLAY_SCALING_FORCE, Settings.Global.DISPLAY_SIZE_FORCED, Settings.Global.DNS_RESOLVER_MAX_SAMPLES, @@ -386,7 +389,6 @@ public class SettingsBackupTest { Settings.Global.TZINFO_UPDATE_METADATA_URL, Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, - Settings.Global.UID_CPUPOWER, Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, @@ -441,7 +443,7 @@ public class SettingsBackupTest { Settings.Global.WIFI_WATCHDOG_ON, Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON, Settings.Global.WINDOW_ANIMATION_SCALE, - Settings.Global.WIRELESS_CHARGING_STARTED_SOUND, + Settings.Global.CHARGING_STARTED_SOUND, Settings.Global.WTF_IS_FATAL, Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_CONFIG_ETAG, diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 69e5670f02d5..7f4f9f7b928a 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -66,6 +66,7 @@ import android.support.test.espresso.action.EspressoKey; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; import android.test.suitebuilder.annotation.Suppress; import android.text.InputType; import android.text.Selection; @@ -311,15 +312,69 @@ public class TextViewActivityTest { @Test public void testToolbarAppearsAfterLinkClicked() throws Throwable { - runToolbarAppearsAfterLinkClickedTest(R.id.textview); + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview); + int position = (textLink.getStart() + textLink.getEnd()) / 2; + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + } @Test public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable { - runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview); + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + int position = (textLink.getStart() + textLink.getEnd()) / 2; + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + } + + @Test + public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable { + // Add a link to both selectable and nonselectable TextViews: + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview); + int selectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; + textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; + TextView selectableTextView = mActivity.findViewById(R.id.textview); + TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview); + + onView(withId(R.id.nonselectable_textview)) + .perform(clickOnTextAtIndex(nonselectablePosition)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + assertTrue(nonselectableTextView.hasSelection()); + + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(selectablePosition)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + assertTrue(selectableTextView.hasSelection()); + assertFalse(nonselectableTextView.hasSelection()); + } + + @Test + public void testSelectionRemovedFromNonselectableTextWhenWindowLosesFocus() throws Throwable { + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; + TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview); + + onView(withId(R.id.nonselectable_textview)) + .perform(clickOnTextAtIndex(nonselectablePosition)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + assertTrue(nonselectableTextView.hasSelection()); + + UiDevice device = UiDevice.getInstance(mInstrumentation); + device.openNotification(); + Thread.sleep(2000); + device.pressBack(); + Thread.sleep(2000); + + assertFalse(nonselectableTextView.hasSelection()); } - private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable { + private TextLinks.TextLink addLinkifiedTextToTextView(int id) throws Throwable { TextView textView = mActivity.findViewById(id); useSystemDefaultTextClassifier(); TextClassificationManager textClassificationManager = @@ -338,11 +393,7 @@ public class TextViewActivityTest { // Wait for the UI thread to refresh Thread.sleep(1000); - TextLinks.TextLink textLink = links.getLinks().iterator().next(); - int position = (textLink.getStart() + textLink.getEnd()) / 2; - onView(withId(id)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + return links.getLinks().iterator().next(); } @Test diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java index 3919fdd7082f..41082b7c7593 100644 --- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java @@ -127,7 +127,7 @@ public class MessagingLinearLayoutTest { assertEquals(355, mView.getMeasuredHeight());; } - private class FakeImageFloatingTextView extends MessagingMessage { + private class FakeImageFloatingTextView extends MessagingTextMessage { public static final int LINE_HEIGHT = 50; private final int mNumLines; diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk index d8e1fc158fc1..b48a46bbda9a 100644 --- a/core/tests/overlaytests/host/Android.mk +++ b/core/tests/overlaytests/host/Android.mk @@ -24,7 +24,11 @@ LOCAL_TARGET_REQUIRED_MODULES := \ OverlayHostTests_BadSignatureOverlay \ OverlayHostTests_PlatformSignatureStaticOverlay \ OverlayHostTests_PlatformSignatureOverlay \ - OverlayHostTests_PlatformSignatureOverlayV2 + OverlayHostTests_UpdateOverlay \ + OverlayHostTests_FrameworkOverlayV1 \ + OverlayHostTests_FrameworkOverlayV2 \ + OverlayHostTests_AppOverlayV1 \ + OverlayHostTests_AppOverlayV2 include $(BUILD_HOST_JAVA_LIBRARY) # Include to build test-apps. diff --git a/core/tests/overlaytests/host/AndroidTest.xml b/core/tests/overlaytests/host/AndroidTest.xml index 68846238c5f1..1f750a85bc13 100644 --- a/core/tests/overlaytests/host/AndroidTest.xml +++ b/core/tests/overlaytests/host/AndroidTest.xml @@ -18,6 +18,11 @@ <option name="test-tag" value="OverlayHostTests" /> <option name="test-suite-tag" value="apct" /> + <!-- Install the device tests that will be used to verify things on device. --> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="OverlayHostTests_UpdateOverlay.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest"> <option name="class" value="com.android.server.om.hosttest.InstallOverlayTests" /> </test> diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java index 509371085055..bf91a16cb3b7 100644 --- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -23,14 +23,48 @@ import static org.junit.Assert.fail; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) public class InstallOverlayTests extends BaseHostJUnit4Test { - - private static final String OVERLAY_PACKAGE_NAME = + private static final String SIG_OVERLAY_PACKAGE_NAME = "com.android.server.om.hosttest.signature_overlay"; + private static final String APP_OVERLAY_PACKAGE_NAME = + "com.android.server.om.hosttest.app_overlay"; + private static final String FRAMEWORK_OVERLAY_PACKAGE_NAME = + "com.android.server.om.hosttest.framework_overlay"; + private static final String[] ALL_PACKAGES = new String[] { + SIG_OVERLAY_PACKAGE_NAME, APP_OVERLAY_PACKAGE_NAME, FRAMEWORK_OVERLAY_PACKAGE_NAME + }; + + private static final String DEVICE_TEST_PKG = + "com.android.server.om.hosttest.update_overlay_test"; + private static final String DEVICE_TEST_CLS = DEVICE_TEST_PKG + ".UpdateOverlayTest"; + + @Before + public void ensureNoOverlays() throws Exception { + // Make sure we're starting with a clean slate. + for (String pkg : ALL_PACKAGES) { + assertFalse(pkg + " should not be installed", isPackageInstalled(pkg)); + assertFalse(pkg + " should not be registered with overlay manager service", + overlayManagerContainsPackage(pkg)); + } + } + + /* + For some reason, SuiteApkInstaller is *not* uninstalling overlays, even though #installPackage() + claims it will auto-clean. + TODO(b/72877546): Remove when auto-clean is fixed. + */ + @After + public void uninstallOverlays() throws Exception { + for (String pkg : ALL_PACKAGES) { + uninstallPackage(pkg); + } + } @Test public void failToInstallNonPlatformSignedOverlay() throws Exception { @@ -40,7 +74,7 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { } catch (Exception e) { // Expected. } - assertFalse(overlayManagerContainsPackage()); + assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME)); } @Test @@ -51,28 +85,64 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { } catch (Exception e) { // Expected. } - assertFalse(overlayManagerContainsPackage()); + assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME)); } @Test - public void succeedToInstallPlatformSignedOverlay() throws Exception { + public void installPlatformSignedOverlay() throws Exception { installPackage("OverlayHostTests_PlatformSignatureOverlay.apk"); - assertTrue(overlayManagerContainsPackage()); + assertTrue(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME)); } @Test - public void succeedToInstallPlatformSignedOverlayAndUpdate() throws Exception { - installPackage("OverlayHostTests_PlatformSignatureOverlay.apk"); - assertTrue(overlayManagerContainsPackage()); - assertEquals("v1", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName()); + public void installPlatformSignedAppOverlayAndUpdate() throws Exception { + assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource")); + + installPackage("OverlayHostTests_AppOverlayV1.apk"); + setOverlayEnabled(APP_OVERLAY_PACKAGE_NAME, true); + assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME)); + assertEquals("v1", getDevice() + .getAppPackageInfo(APP_OVERLAY_PACKAGE_NAME) + .getVersionName()); + assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, + "expectAppOverlayV1Resource")); + + installPackage("OverlayHostTests_AppOverlayV2.apk"); + assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME)); + assertEquals("v2", getDevice() + .getAppPackageInfo(APP_OVERLAY_PACKAGE_NAME) + .getVersionName()); + assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, + "expectAppOverlayV2Resource")); + } + + @Test + public void installPlatformSignedFrameworkOverlayAndUpdate() throws Exception { + assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource")); + + installPackage("OverlayHostTests_FrameworkOverlayV1.apk"); + setOverlayEnabled(FRAMEWORK_OVERLAY_PACKAGE_NAME, true); + assertTrue(overlayManagerContainsPackage(FRAMEWORK_OVERLAY_PACKAGE_NAME)); + assertEquals("v1", getDevice() + .getAppPackageInfo(FRAMEWORK_OVERLAY_PACKAGE_NAME) + .getVersionName()); + assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, + "expectFrameworkOverlayV1Resource")); + + installPackage("OverlayHostTests_FrameworkOverlayV2.apk"); + assertTrue(overlayManagerContainsPackage(FRAMEWORK_OVERLAY_PACKAGE_NAME)); + assertEquals("v2", getDevice() + .getAppPackageInfo(FRAMEWORK_OVERLAY_PACKAGE_NAME) + .getVersionName()); + assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, + "expectFrameworkOverlayV2Resource")); + } - installPackage("OverlayHostTests_PlatformSignatureOverlayV2.apk"); - assertTrue(overlayManagerContainsPackage()); - assertEquals("v2", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName()); + private void setOverlayEnabled(String pkg, boolean enabled) throws Exception { + getDevice().executeShellCommand("cmd overlay " + (enabled ? "enable " : "disable ") + pkg); } - private boolean overlayManagerContainsPackage() throws Exception { - return getDevice().executeShellCommand("cmd overlay list") - .contains(OVERLAY_PACKAGE_NAME); + private boolean overlayManagerContainsPackage(String pkg) throws Exception { + return getDevice().executeShellCommand("cmd overlay list").contains(pkg); } } diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk index b051a82c29ac..424954947d32 100644 --- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk @@ -40,13 +40,4 @@ LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 include $(BUILD_PACKAGE) -include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := tests -LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlayV2 -LOCAL_COMPATIBILITY_SUITE := general-tests -LOCAL_CERTIFICATE := platform -LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 -LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 -include $(BUILD_PACKAGE) - my_package_prefix := diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk new file mode 100644 index 000000000000..bd6d73dddcab --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk @@ -0,0 +1,73 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test +include $(BUILD_PACKAGE) + +my_package_prefix := com.android.server.om.hosttest.framework_overlay + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1 +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_CERTIFICATE := platform +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 +LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res +LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml +include $(BUILD_PACKAGE) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2 +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_CERTIFICATE := platform +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 +LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res +LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml +include $(BUILD_PACKAGE) + +my_package_prefix := com.android.server.om.hosttest.app_overlay + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1 +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_CERTIFICATE := platform +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 +LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res +LOCAL_MANIFEST_FILE := app/AndroidManifest.xml +include $(BUILD_PACKAGE) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2 +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_CERTIFICATE := platform +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 +LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res +LOCAL_MANIFEST_FILE := app/AndroidManifest.xml +include $(BUILD_PACKAGE) + +my_package_prefix := diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..06077a79d009 --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.om.hosttest.update_overlay_test"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.om.hosttest.update_overlay_test" + android:label="Update Overlay Test"/> +</manifest> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml new file mode 100644 index 000000000000..73804ebd211f --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.om.hosttest.app_overlay"> + <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test" /> +</manifest> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml new file mode 100644 index 000000000000..63f85c2cb32c --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="app_resource">App Resource Overlay V1</string> +</resources> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml new file mode 100644 index 000000000000..fa4a6977e77b --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="app_resource">App Resource Overlay V2</string> +</resources> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml new file mode 100644 index 000000000000..8c8fe9480961 --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.om.hosttest.framework_overlay"> + <overlay android:targetPackage="android" /> +</manifest> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml new file mode 100644 index 000000000000..fedb2c68103c --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="ok">Framework Overlay V1</string> +</resources> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml new file mode 100644 index 000000000000..8aebf48301a7 --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="ok">Framework Overlay V2</string> +</resources> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml new file mode 100644 index 000000000000..7393166c05ee --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="app_resource">App Resource</string> +</resources> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java new file mode 100644 index 000000000000..d46bb378971d --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java @@ -0,0 +1,77 @@ +/* + * 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.server.om.hosttest.update_overlay_test; + +import static org.junit.Assert.assertEquals; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UpdateOverlayTest { + private Resources mResources; + + @Before + public void setUp() throws Exception { + final Configuration defaultLocaleConfiguration = new Configuration(); + defaultLocaleConfiguration.setLocale(Locale.US); + mResources = InstrumentationRegistry + .getInstrumentation() + .getContext() + .createConfigurationContext(defaultLocaleConfiguration) + .getResources(); + } + + @Test + public void expectAppResource() throws Exception { + assertEquals("App Resource", mResources.getString(R.string.app_resource)); + } + + @Test + public void expectAppOverlayV1Resource() throws Exception { + assertEquals("App Resource Overlay V1", mResources.getString(R.string.app_resource)); + } + + @Test + public void expectAppOverlayV2Resource() throws Exception { + assertEquals("App Resource Overlay V2", mResources.getString(R.string.app_resource)); + } + + @Test + public void expectFrameworkOverlayResource() throws Exception { + assertEquals("OK", mResources.getString(android.R.string.ok)); + } + + @Test + public void expectFrameworkOverlayV1Resource() throws Exception { + assertEquals("Framework Overlay V1", mResources.getString(android.R.string.ok)); + } + + @Test + public void expectFrameworkOverlayV2Resource() throws Exception { + assertEquals("Framework Overlay V2", mResources.getString(android.R.string.ok)); + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index bb9b89be9edb..3495b84e0ab9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -158,7 +158,6 @@ applications that come with the platform <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> - <permission name="android.permission.PACKAGE_USAGE_STATS"/> <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index acefead785c4..3cca47b47a59 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -25,7 +25,7 @@ import android.annotation.Nullable; import android.annotation.RawRes; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; +import android.content.res.AssetManager.AssetInputStream; import android.content.res.Resources; import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.Drawable; @@ -263,6 +263,63 @@ public final class ImageDecoder implements AutoCloseable { } } + /** + * Takes ownership of the AssetInputStream. + * + * @hide + */ + public static class AssetInputStreamSource extends Source { + public AssetInputStreamSource(@NonNull AssetInputStream ais, + @NonNull Resources res, @NonNull TypedValue value) { + mAssetInputStream = ais; + mResources = res; + + if (value.density == TypedValue.DENSITY_DEFAULT) { + mDensity = DisplayMetrics.DENSITY_DEFAULT; + } else if (value.density != TypedValue.DENSITY_NONE) { + mDensity = value.density; + } else { + mDensity = Bitmap.DENSITY_NONE; + } + } + + private AssetInputStream mAssetInputStream; + private final Resources mResources; + private final int mDensity; + + @Override + public Resources getResources() { return mResources; } + + @Override + public int getDensity() { + return mDensity; + } + + @Override + public ImageDecoder createImageDecoder() throws IOException { + ImageDecoder decoder = null; + synchronized (this) { + if (mAssetInputStream == null) { + throw new IOException("Cannot reuse AssetInputStreamSource"); + } + AssetInputStream ais = mAssetInputStream; + mAssetInputStream = null; + try { + long asset = ais.getNativeAsset(); + decoder = nCreate(asset); + } finally { + if (decoder == null) { + IoUtils.closeQuietly(ais); + } else { + decoder.mInputStream = ais; + decoder.mOwnsInputStream = true; + } + } + return decoder; + } + } + } + private static class ResourceSource extends Source { ResourceSource(@NonNull Resources res, int resId) { mResources = res; @@ -296,11 +353,7 @@ public final class ImageDecoder implements AutoCloseable { mResDensity = value.density; } - if (!(is instanceof AssetManager.AssetInputStream)) { - // This should never happen. - throw new RuntimeException("Resource is not an asset?"); - } - long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); + long asset = ((AssetInputStream) is).getNativeAsset(); decoder = nCreate(asset); } finally { if (decoder == null) { diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 04c5295539ac..f41267eacde6 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -60,6 +60,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Arrays; @@ -801,36 +802,18 @@ public class Typeface { * @return The new typeface. */ public static Typeface createFromAsset(AssetManager mgr, String path) { - if (path == null) { - throw new NullPointerException(); // for backward compatibility - } - synchronized (sDynamicCacheLock) { - Typeface typeface = new Builder(mgr, path).build(); - if (typeface != null) return typeface; - - final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, - null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, - DEFAULT_FAMILY); - typeface = sDynamicTypefaceCache.get(key); - if (typeface != null) return typeface; + Preconditions.checkNotNull(path); // for backward compatibility + Preconditions.checkNotNull(mgr); - final FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, - 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, - null /* axes */)) { - if (!fontFamily.freeze()) { - return Typeface.DEFAULT; - } - final FontFamily[] families = { fontFamily }; - typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - sDynamicTypefaceCache.put(key, typeface); - return typeface; - } else { - fontFamily.abortCreation(); - } + Typeface typeface = new Builder(mgr, path).build(); + if (typeface != null) return typeface; + // check if the file exists, and throw an exception for backward compatibility + try (InputStream inputStream = mgr.open(path)) { + } catch (IOException e) { + throw new RuntimeException("Font asset not found " + path); } - throw new RuntimeException("Font asset not found " + path); + + return Typeface.DEFAULT; } /** @@ -848,13 +831,22 @@ public class Typeface { /** * Create a new typeface from the specified font file. * - * @param path The path to the font data. + * @param file The path to the font data. * @return The new typeface. */ - public static Typeface createFromFile(@Nullable File path) { + public static Typeface createFromFile(@Nullable File file) { // For the compatibility reasons, leaving possible NPE here. // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull - return createFromFile(path.getAbsolutePath()); + + Typeface typeface = new Builder(file).build(); + if (typeface != null) return typeface; + + // check if the file exists, and throw an exception for backward compatibility + if (!file.exists()) { + throw new RuntimeException("Font asset not found " + file.getAbsolutePath()); + } + + return Typeface.DEFAULT; } /** @@ -864,19 +856,8 @@ public class Typeface { * @return The new typeface. */ public static Typeface createFromFile(@Nullable String path) { - final FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) { - if (!fontFamily.freeze()) { - return Typeface.DEFAULT; - } - FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - } else { - fontFamily.abortCreation(); - } - throw new RuntimeException("Font not found " + path); + Preconditions.checkNotNull(path); // for backward compatibility + return createFromFile(new File(path)); } /** diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 36a4d26d62bb..44ba78557678 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1171,6 +1171,8 @@ public abstract class Drawable { /** * Create a drawable from an inputstream, using the given resources and * value to determine density information. + * + * @deprecated Prefer the version without an Options object. */ public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) { diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 251b2e773cfb..70d52164ff74 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -145,6 +145,7 @@ cc_test { "tests/TypeWrappers_test.cpp", "tests/ZipUtils_test.cpp", ], + static_libs: ["libgmock"], target: { android: { srcs: [ @@ -171,6 +172,7 @@ cc_benchmark { // Actual benchmarks. "tests/AssetManager2_bench.cpp", + "tests/AttributeResolution_bench.cpp", "tests/SparseEntry_bench.cpp", "tests/Theme_bench.cpp", ], diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 415d3e36adf9..a8c916bbaea6 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -36,6 +36,31 @@ namespace android { +struct FindEntryResult { + // A pointer to the resource table entry for this resource. + // If the size of the entry is > sizeof(ResTable_entry), it can be cast to + // a ResTable_map_entry and processed as a bag/map. + const ResTable_entry* entry; + + // The configuration for which the resulting entry was defined. This is already swapped to host + // endianness. + ResTable_config config; + + // The bitmask of configuration axis with which the resource value varies. + uint32_t type_flags; + + // The dynamic package ID map for the package from which this resource came from. + const DynamicRefTable* dynamic_ref_table; + + // The string pool reference to the type's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef type_string_ref; + + // The string pool reference to the entry's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef entry_string_ref; +}; + AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); } @@ -44,6 +69,7 @@ bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets bool invalidate_caches) { apk_assets_ = apk_assets; BuildDynamicRefTable(); + RebuildFilterList(); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } @@ -74,12 +100,14 @@ void AssetManager2::BuildDynamicRefTable() { if (idx == 0xff) { package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size()); package_groups_.push_back({}); - package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id; + DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table; + ref_table.mAssignedPackageId = package_id; + ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f; } PackageGroup* package_group = &package_groups_[idx]; // Add the package and to the set of packages with the same ID. - package_group->packages_.push_back(package.get()); + package_group->packages_.push_back(ConfiguredPackage{package.get(), {}}); package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i)); // Add the package name -> build time ID mappings. @@ -94,7 +122,7 @@ void AssetManager2::BuildDynamicRefTable() { // Now assign the runtime IDs so that we have a build-time to runtime ID map. const auto package_groups_end = package_groups_.end(); for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { - const std::string& package_name = iter->packages_[0]->GetPackageName(); + const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName(); for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()), iter->dynamic_ref_table.mAssignedPackageId); @@ -105,20 +133,33 @@ void AssetManager2::BuildDynamicRefTable() { void AssetManager2::DumpToLog() const { base::ScopedLogSeverity _log(base::INFO); + LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this); + std::string list; + for (const auto& apk_assets : apk_assets_) { + base::StringAppendF(&list, "%s,", apk_assets->GetPath().c_str()); + } + LOG(INFO) << "ApkAssets: " << list; + + list = ""; for (size_t i = 0; i < package_ids_.size(); i++) { if (package_ids_[i] != 0xff) { - base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]); + base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]); } } LOG(INFO) << "Package ID map: " << list; for (const auto& package_group: package_groups_) { - list = ""; - for (const auto& package : package_group.packages_) { - base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId()); - } - LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list; + list = ""; + for (const auto& package : package_group.packages_) { + const LoadedPackage* loaded_package = package.loaded_package_; + base::StringAppendF(&list, "%s(%02x%s), ", loaded_package->GetPackageName().c_str(), + loaded_package->GetPackageId(), + (loaded_package->IsDynamic() ? " dynamic" : "")); + } + LOG(INFO) << base::StringPrintf("PG (%02x): ", + package_group.dynamic_ref_table.mAssignedPackageId) + << list; } } @@ -157,52 +198,54 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) { configuration_ = configuration; if (diff) { + RebuildFilterList(); InvalidateCaches(static_cast<uint32_t>(diff)); } } std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system, - bool exclude_mipmap) { + bool exclude_mipmap) const { ATRACE_CALL(); std::set<ResTable_config> configurations; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectConfigurations(exclude_mipmap, &configurations); + package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations); } } return configurations; } std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, - bool merge_equivalent_languages) { + bool merge_equivalent_languages) const { ATRACE_CALL(); std::set<std::string> locales; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectLocales(merge_equivalent_languages, &locales); + package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales); } } return locales; } -std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) { +std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, mode); } std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode) { + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, cookie, mode); } -std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { +std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const { ATRACE_CALL(); std::string full_path = "assets/" + dirname; @@ -236,7 +279,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { // is inconsistent for split APKs. std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie) { + ApkAssetsCookie* out_cookie) const { ATRACE_CALL(); for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) { std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode); @@ -255,7 +298,8 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, - ApkAssetsCookie cookie, Asset::AccessMode mode) { + ApkAssetsCookie cookie, + Asset::AccessMode mode) const { ATRACE_CALL(); if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) { return {}; @@ -264,14 +308,13 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, - bool stop_at_first_match, FindEntryResult* out_entry) { - ATRACE_CALL(); - + bool /*stop_at_first_match*/, + FindEntryResult* out_entry) const { // Might use this if density_override != 0. ResTable_config density_override_config; // Select our configuration or generate a density override configuration. - ResTable_config* desired_config = &configuration_; + const ResTable_config* desired_config = &configuration_; if (density_override != 0 && density_override != configuration_.density) { density_override_config = configuration_; density_override_config.density = density_override; @@ -285,53 +328,135 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri const uint32_t package_id = get_package_id(resid); const uint8_t type_idx = get_type_id(resid) - 1; - const uint16_t entry_id = get_entry_id(resid); + const uint16_t entry_idx = get_entry_id(resid); - const uint8_t idx = package_ids_[package_id]; - if (idx == 0xff) { + const uint8_t package_idx = package_ids_[package_id]; + if (package_idx == 0xff) { LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid); return kInvalidCookie; } - FindEntryResult best_entry; - ApkAssetsCookie best_cookie = kInvalidCookie; - uint32_t cumulated_flags = 0u; - - const PackageGroup& package_group = package_groups_[idx]; + const PackageGroup& package_group = package_groups_[package_idx]; const size_t package_count = package_group.packages_.size(); - FindEntryResult current_entry; - for (size_t i = 0; i < package_count; i++) { - const LoadedPackage* loaded_package = package_group.packages_[i]; - if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) { + + ApkAssetsCookie best_cookie = kInvalidCookie; + const LoadedPackage* best_package = nullptr; + const ResTable_type* best_type = nullptr; + const ResTable_config* best_config = nullptr; + ResTable_config best_config_copy; + uint32_t best_offset = 0u; + uint32_t type_flags = 0u; + + // If desired_config is the same as the set configuration, then we can use our filtered list + // and we don't need to match the configurations, since they already matched. + const bool use_fast_path = desired_config == &configuration_; + + for (size_t pi = 0; pi < package_count; pi++) { + const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; + const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_; + ApkAssetsCookie cookie = package_group.cookies_[pi]; + + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx); + if (UNLIKELY(type_spec == nullptr)) { continue; } - cumulated_flags |= current_entry.type_flags; + uint16_t local_entry_idx = entry_idx; - const ResTable_config* current_config = current_entry.config; - const ResTable_config* best_config = best_entry.config; - if (best_cookie == kInvalidCookie || - current_config->isBetterThan(*best_config, desired_config) || - (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) { - best_entry = current_entry; - best_cookie = package_group.cookies_[i]; - if (stop_at_first_match) { - break; + // If there is an IDMAP supplied with this package, translate the entry ID. + if (type_spec->idmap_entries != nullptr) { + if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) { + // There is no mapping, so the resource is not meant to be in this overlay package. + continue; + } + } + + type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx); + + // If the package is an overlay, then even configurations that are the same MUST be chosen. + const bool package_is_overlay = loaded_package->IsOverlay(); + + const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; + if (use_fast_path) { + const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations; + const size_t type_count = candidate_configs.size(); + for (uint32_t i = 0; i < type_count; i++) { + const ResTable_config& this_config = candidate_configs[i]; + + // We can skip calling ResTable_config::match() because we know that all candidate + // configurations that do NOT match have been filtered-out. + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const ResTable_type* type_chunk = filtered_group.types[i]; + const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = type_chunk; + best_config = &this_config; + best_offset = offset; + } + } + } else { + // This is the slower path, which doesn't use the filtered list of configurations. + // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness + // and fill in any new fields that did not exist when the APK was compiled. + // Furthermore when selecting configurations we can't just record the pointer to the + // ResTable_config, we must copy it. + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + + if (this_config.match(*desired_config)) { + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = *iter; + best_config_copy = this_config; + best_config = &best_config_copy; + best_offset = offset; + } + } } } } - if (best_cookie == kInvalidCookie) { + if (UNLIKELY(best_cookie == kInvalidCookie)) { + return kInvalidCookie; + } + + const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset); + if (UNLIKELY(best_entry == nullptr)) { return kInvalidCookie; } - *out_entry = best_entry; + out_entry->entry = best_entry; + out_entry->config = *best_config; + out_entry->type_flags = type_flags; + out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1); + out_entry->entry_string_ref = + StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index); out_entry->dynamic_ref_table = &package_group.dynamic_ref_table; - out_entry->type_flags = cumulated_flags; return best_cookie; } -bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { +bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const { ATRACE_CALL(); FindEntryResult entry; @@ -341,7 +466,8 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return false; } - const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid); + const LoadedPackage* package = + apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); if (package == nullptr) { return false; } @@ -369,7 +495,7 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return true; } -bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { +bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const { FindEntryResult entry; ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); @@ -383,7 +509,7 @@ bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags) { + uint32_t* out_flags) const { ATRACE_CALL(); FindEntryResult entry; @@ -402,7 +528,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Create a reference since we can't represent this complex type as a Res_value. out_value->dataType = Res_value::TYPE_REFERENCE; out_value->data = resid; - *out_selected_config = *entry.config; + *out_selected_config = entry.config; *out_flags = entry.type_flags; return cookie; } @@ -414,7 +540,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Convert the package ID to the runtime assigned package ID. entry.dynamic_ref_table->lookupResourceValue(out_value); - *out_selected_config = *entry.config; + *out_selected_config = entry.config; *out_flags = entry.type_flags; return cookie; } @@ -422,16 +548,14 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference) { + uint32_t* out_last_reference) const { ATRACE_CALL(); constexpr const int kMaxIterations = 20; for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE && in_out_value->data != 0u && iteration < kMaxIterations; iteration++) { - if (out_last_reference != nullptr) { - *out_last_reference = in_out_value->data; - } + *out_last_reference = in_out_value->data; uint32_t new_flags = 0u; cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/, in_out_value, in_out_selected_config, &new_flags); @@ -492,7 +616,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Attributes, arrays, etc don't have a resource id as the name. They specify // other data, which would be wrong to change via a lookup. if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } @@ -524,7 +649,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { const ResolvedBag* parent_bag = GetBag(parent_resid); if (parent_bag == nullptr) { // Failed to get the parent that should exist. - LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid); + LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, + resid); return nullptr; } @@ -543,7 +669,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t child_key = dtohl(map_entry->name.ident); if (!is_internal_resid(child_key)) { if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, + resid); return nullptr; } } @@ -582,7 +709,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t new_key = dtohl(map_entry->name.ident); if (!is_internal_resid(new_key)) { if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } @@ -638,7 +766,7 @@ static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) { uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const std::string& fallback_type, - const std::string& fallback_package) { + const std::string& fallback_package) const { StringPiece package_name, type, entry; if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) { return 0u; @@ -670,7 +798,8 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const static std::u16string kAttrPrivate16 = u"^attr-private"; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { + for (const ConfiguredPackage& package_impl : package_group.packages_) { + const LoadedPackage* package = package_impl.loaded_package_; if (package_name != package->GetPackageName()) { // All packages in the same group are expected to have the same package name. break; @@ -692,6 +821,32 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, return 0u; } +void AssetManager2::RebuildFilterList() { + for (PackageGroup& group : package_groups_) { + for (ConfiguredPackage& impl : group.packages_) { + // Destroy it. + impl.filtered_configs_.~ByteBucketArray(); + + // Re-create it. + new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); + + // Create the filters here. + impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) { + FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index); + const auto iter_end = spec->types + spec->type_count; + for (auto iter = spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + if (this_config.match(configuration_)) { + group.configurations.push_back(this_config); + group.types.push_back(*iter); + } + } + }); + } + } +} + void AssetManager2::InvalidateCaches(uint32_t diff) { if (diff == 0xffffffffu) { // Everything must go. @@ -872,7 +1027,7 @@ ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value, ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_type_spec_flags, - uint32_t* out_last_ref) { + uint32_t* out_last_ref) const { if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) { uint32_t new_flags; cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags); diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 60e3845d98a9..f912af4f7190 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -20,13 +20,18 @@ #include <log/log.h> +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeFinder.h" -#include "androidfw/ResourceTypes.h" constexpr bool kDebugStyles = false; namespace android { +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1); +} + class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> { public: @@ -44,58 +49,53 @@ class XmlAttributeFinder }; class BagAttributeFinder - : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> { + : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> { public: - BagAttributeFinder(const ResTable::bag_entry* start, - const ResTable::bag_entry* end) - : BackTrackingAttributeFinder(start, end) {} + BagAttributeFinder(const ResolvedBag* bag) + : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr, + bag != nullptr ? bag->entries + bag->entry_count : nullptr) { + } - inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const { - return entry->map.name.ident; + inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const { + return entry->key; } }; -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, - uint32_t def_style_res, uint32_t* src_values, - size_t src_values_length, uint32_t* attrs, - size_t attrs_length, uint32_t* out_values, - uint32_t* out_indices) { +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, + uint32_t* src_values, size_t src_values_length, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, def_style_attr, def_style_res); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { def_style_res = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_end = - def_style_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_res != 0) { + default_style_bag = assetmanager->GetBag(def_style_res); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -106,7 +106,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = -1; + ApkAssetsCookie cookie = kInvalidCookie; uint32_t type_set_flags = 0; value.dataType = Res_value::TYPE_NULL; @@ -122,15 +122,14 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, value.dataType = Res_value::TYPE_ATTRIBUTE; value.data = src_values[ii]; if (kDebugStyles) { - ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, - value.data); + ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data); } } else { - const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_entry != def_style_end) { - block = def_style_entry->stringBlock; - type_set_flags = def_style_type_set_flags; - value = def_style_entry->map.value; + const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -140,22 +139,26 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t resid = 0; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); } } else if (value.data != Res_value::DATA_NULL_EMPTY) { - // If we still don't have a value for this attribute, try to find - // it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + // If we still don't have a value for this attribute, try to find it in the theme! + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -169,7 +172,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = -1; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -179,9 +182,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != -1 ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -195,90 +196,80 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } return true; } -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { - ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", - theme, def_style_attr, def_style_res, xml_parser); + ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme, + def_style_attr, def_style_resid, xml_parser); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, - &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { - def_style_res = value.data; + def_style_resid = value.data; } } } - // Retrieve the style class associated with the current XML tag. - int style = 0; - uint32_t style_bag_type_set_flags = 0; + // Retrieve the style resource ID associated with the current XML tag's style attribute. + uint32_t style_resid = 0u; + uint32_t style_flags = 0u; if (xml_parser != nullptr) { ssize_t idx = xml_parser->indexOfStyle(); if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) { if (value.dataType == value.TYPE_ATTRIBUTE) { - if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) { + // Resolve the attribute with out theme. + if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) { value.dataType = Res_value::TYPE_NULL; } } + if (value.dataType == value.TYPE_REFERENCE) { - style = value.data; + style_resid = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_attr_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_attr_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_attr_end = - def_style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_attr_start, - def_style_attr_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_resid != 0) { + default_style_bag = assetmanager->GetBag(def_style_resid); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Retrieve the style class bag, if requested. - const ResTable::bag_entry* style_attr_start = nullptr; - uint32_t style_type_set_flags = 0; - bag_off = - style != 0 - ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags) - : -1; - style_type_set_flags |= style_bag_type_set_flags; - const ResTable::bag_entry* const style_attr_end = - style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end); + const ResolvedBag* xml_style_bag = nullptr; + if (style_resid != 0) { + xml_style_bag = assetmanager->GetBag(style_resid); + if (xml_style_bag != nullptr) { + style_flags |= xml_style_bag->type_spec_flags; + } + } + + BagAttributeFinder xml_style_attr_finder(xml_style_bag); // Retrieve the XML attributes, if requested. - static const ssize_t kXmlBlock = 0x10000000; XmlAttributeFinder xml_attr_finder(xml_parser); - const size_t xml_attr_end = - xml_parser != nullptr ? xml_parser->getAttributeCount() : 0; // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -289,8 +280,8 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -302,7 +293,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Walk through the xml attributes looking for the requested attribute. const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident); - if (xml_attr_idx != xml_attr_end) { + if (xml_attr_idx != xml_attr_finder.end()) { // We found the attribute we were looking for. xml_parser->getAttributeValue(xml_attr_idx, &value); if (kDebugStyles) { @@ -312,12 +303,12 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the style class values looking for the requested attribute. - const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident); - if (style_attr_entry != style_attr_end) { + const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident); + if (entry != xml_style_attr_finder.end()) { // We found the attribute we were looking for. - block = style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -326,25 +317,25 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the default style values looking for the requested attribute. - const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_attr_entry != def_style_attr_end) { + const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { // We found the attribute we were looking for. - block = def_style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = def_style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } } } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -352,14 +343,15 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -375,7 +367,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -385,9 +377,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -402,36 +392,28 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - // out_indices must NOT be nullptr. out_indices[0] = indices_idx; } -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, - uint32_t* attrs, size_t attrs_length, - uint32_t* out_values, uint32_t* out_indices) { +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { ResTable_config config; Res_value value; int indices_idx = 0; - // Now lock down the resource object and start pulling stuff from it. - res->lock(); - // Retrieve the XML attributes, if requested. const size_t xml_attr_count = xml_parser->getAttributeCount(); size_t ix = 0; uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix); - static const ssize_t kXmlBlock = 0x10000000; - // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -450,28 +432,27 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, cur_xml_attr = xml_parser->getAttributeNameResID(ix); } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - // printf("Resolving attribute reference\n"); - ssize_t new_block = res->resolveReference(&value, block, &resid, - &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } } // Deal with the special @null value -- it turns back to TYPE_NULL. if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -485,8 +466,6 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, out_values += STYLE_NUM_ENTRIES; } - res->unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 28548e27baf0..1d2c597c4c8c 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -44,44 +44,6 @@ namespace android { constexpr const static int kAppPackageId = 0x7f; -// Element of a TypeSpec array. See TypeSpec. -struct Type { - // The configuration for which this type defines entries. - // This is already converted to host endianness. - ResTable_config configuration; - - // Pointer to the mmapped data where entry definitions are kept. - const ResTable_type* type; -}; - -// TypeSpec is going to be immediately proceeded by -// an array of Type structs, all in the same block of memory. -struct TypeSpec { - // Pointer to the mmapped data where flags are kept. - // Flags denote whether the resource entry is public - // and under which configurations it varies. - const ResTable_typeSpec* type_spec; - - // Pointer to the mmapped data where the IDMAP mappings for this type - // exist. May be nullptr if no IDMAP exists. - const IdmapEntry_header* idmap_entries; - - // The number of types that follow this struct. - // There is a type for each configuration - // that entries are defined for. - size_t type_count; - - // Trick to easily access a variable number of Type structs - // proceeding this struct, and to ensure their alignment. - const Type types[0]; -}; - -// TypeSpecPtr points to the block of memory that holds -// a TypeSpec struct, followed by an array of Type structs. -// TypeSpecPtr is a managed pointer that knows how to delete -// itself. -using TypeSpecPtr = util::unique_cptr<TypeSpec>; - namespace { // Builder that helps accumulate Type structs and then create a single @@ -95,21 +57,22 @@ class TypeSpecPtrBuilder { } void AddType(const ResTable_type* type) { - ResTable_config config; - config.copyFromDtoH(type->config); - types_.push_back(Type{config, type}); + types_.push_back(type); } TypeSpecPtr Build() { // Check for overflow. - if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) { + using ElementType = const ResTable_type*; + if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) < + types_.size()) { return {}; } - TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type))); + TypeSpec* type_spec = + (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); type_spec->type_spec = header_; type_spec->idmap_entries = idmap_header_; type_spec->type_count = types_.size(); - memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type)); + memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); return TypeSpecPtr(type_spec); } @@ -118,7 +81,7 @@ class TypeSpecPtrBuilder { const ResTable_typeSpec* header_; const IdmapEntry_header* idmap_header_; - std::vector<Type> types_; + std::vector<const ResTable_type*> types_; }; } // namespace @@ -162,18 +125,17 @@ static bool VerifyResTableType(const ResTable_type* header) { return true; } -static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset, - size_t entry_idx) { +static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) { // Check that the offset is aligned. if (entry_offset & 0x03) { - LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned."; + LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned."; return false; } // Check that the offset doesn't overflow. if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) { // Overflow in offset. - LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large."; + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large."; return false; } @@ -181,7 +143,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset entry_offset += dtohl(type->entriesStart); if (entry_offset > chunk_size - sizeof(ResTable_entry)) { - LOG(ERROR) << "Entry offset at index " << entry_idx + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large. No room for ResTable_entry."; return false; } @@ -191,13 +153,13 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset const size_t entry_size = dtohs(entry->size); if (entry_size < sizeof(*entry)) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too small."; return false; } if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too large."; return false; } @@ -205,7 +167,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset if (entry_size < sizeof(ResTable_map_entry)) { // There needs to be room for one Res_value struct. if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) { - LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx + LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset << " for type " << (int)type->id << "."; return false; } @@ -214,12 +176,12 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size); const size_t value_size = dtohs(value->size); if (value_size < sizeof(Res_value)) { - LOG(ERROR) << "Res_value at index " << entry_idx << " is too small."; + LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small."; return false; } if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) { - LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx + LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset << " is too large."; return false; } @@ -228,119 +190,76 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset const size_t map_entry_count = dtohl(map->count); size_t map_entries_start = entry_offset + entry_size; if (map_entries_start & 0x03) { - LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset."; + LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset."; return false; } // Each entry is sizeof(ResTable_map) big. if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) { - LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << "."; + LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << "."; return false; } } return true; } -bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx, - const ResTable_config& config, FindEntryResult* out_entry) const { - const ResTable_config* best_config = nullptr; - const ResTable_type* best_type = nullptr; - uint32_t best_offset = 0; - - for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) { - const Type* type = &type_spec_ptr->types[i]; - const ResTable_type* type_chunk = type->type; - - if (type->configuration.match(config) && - (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) { - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const size_t entry_count = dtohl(type_chunk->entryCount); - const size_t offsets_offset = dtohs(type_chunk->header.headerSize); - - // Check if there is the desired entry in this type. - - if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { - // This is encoded as a sparse map, so perform a binary search. - const ResTable_sparseTypeEntry* sparse_indices = - reinterpret_cast<const ResTable_sparseTypeEntry*>( - reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); - const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; - const ResTable_sparseTypeEntry* result = - std::lower_bound(sparse_indices, sparse_indices_end, entry_idx, - [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { - return dtohs(entry.idx) < entry_idx; - }); - - if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) { - // No entry found. - continue; - } +const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk, + uint16_t entry_index) { + uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index); + if (entry_offset == ResTable_type::NO_ENTRY) { + return nullptr; + } + return GetEntryFromOffset(type_chunk, entry_offset); +} - // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as - // the real offset divided by 4. - best_offset = uint32_t{dtohs(result->offset)} * 4u; - } else { - if (entry_idx >= entry_count) { - // This entry cannot be here. - continue; - } +uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const size_t entry_count = dtohl(type_chunk->entryCount); + const size_t offsets_offset = dtohs(type_chunk->header.headerSize); - const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); - const uint32_t offset = dtohl(entry_offsets[entry_idx]); - if (offset == ResTable_type::NO_ENTRY) { - continue; - } - - // There is an entry for this resource, record it. - best_offset = offset; - } + // Check if there is the desired entry in this type. - best_config = &type->configuration; - best_type = type_chunk; + if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { + // This is encoded as a sparse map, so perform a binary search. + const ResTable_sparseTypeEntry* sparse_indices = + reinterpret_cast<const ResTable_sparseTypeEntry*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; + const ResTable_sparseTypeEntry* result = + std::lower_bound(sparse_indices, sparse_indices_end, entry_index, + [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { + return dtohs(entry.idx) < entry_idx; + }); + + if (result == sparse_indices_end || dtohs(result->idx) != entry_index) { + // No entry found. + return ResTable_type::NO_ENTRY; } - } - if (best_type == nullptr) { - return false; + // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as + // the real offset divided by 4. + return uint32_t{dtohs(result->offset)} * 4u; } - if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) { - return false; + // This type is encoded as a dense array. + if (entry_index >= entry_count) { + // This entry cannot be here. + return ResTable_type::NO_ENTRY; } - const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>( - reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart)); - - const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1); - out_entry->type_flags = dtohl(flags[entry_idx]); - out_entry->entry = best_entry; - out_entry->config = best_config; - out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1); - out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index)); - return true; + const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + return dtohl(entry_offsets[entry_index]); } -bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - FindEntryResult* out_entry) const { - ATRACE_CALL(); - - // If the type IDs are offset in this package, we need to take that into account when searching - // for a type. - const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_]; - if (UNLIKELY(ptr == nullptr)) { - return false; +const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk, + uint32_t offset) { + if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) { + return nullptr; } - - // If there is an IDMAP supplied with this package, translate the entry ID. - if (ptr->idmap_entries != nullptr) { - if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) { - // There is no mapping, so the resource is not meant to be in this overlay package. - return false; - } - } - return FindEntry(ptr, entry_idx, config, out_entry); + return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) + + offset + dtohl(type_chunk->entriesStart)); } void LoadedPackage::CollectConfigurations(bool exclude_mipmap, @@ -348,7 +267,7 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, const static std::u16string kMipMap = u"mipmap"; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { if (exclude_mipmap) { const int type_idx = type_spec->type_spec->id - 1; @@ -369,8 +288,11 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, } } - for (size_t j = 0; j < type_spec->type_count; j++) { - out_configs->insert(type_spec->types[j].configuration); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config config; + config.copyFromDtoH((*iter)->config); + out_configs->insert(config); } } } @@ -380,10 +302,12 @@ void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out char temp_locale[RESTABLE_MAX_LOCALE_LEN]; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { - for (size_t j = 0; j < type_spec->type_count; j++) { - const ResTable_config& configuration = type_spec->types[j].configuration; + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config configuration; + configuration.copyFromDtoH((*iter)->config); if (configuration.locale != 0) { configuration.getBcp47Locale(temp_locale, canonicalize); std::string locale(temp_locale); @@ -411,17 +335,17 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } - for (size_t ti = 0; ti < type_spec->type_count; ti++) { - const Type* type = &type_spec->types[ti]; - size_t entry_count = dtohl(type->type->entryCount); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + const ResTable_type* type = *iter; + size_t entry_count = dtohl(type->entryCount); for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize)); + reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize)); const uint32_t offset = dtohl(entry_offsets[entry_idx]); if (offset != ResTable_type::NO_ENTRY) { - const ResTable_entry* entry = - reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) + - dtohl(type->type->entriesStart) + offset); + const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset); if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). @@ -433,8 +357,7 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } -const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const { - const uint8_t package_id = get_package_id(resid); +const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { for (const auto& loaded_package : packages_) { if (loaded_package->GetPackageId() == package_id) { return loaded_package.get(); @@ -682,26 +605,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, return std::move(loaded_package); } -bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, - FindEntryResult* out_entry) const { - ATRACE_CALL(); - - const uint8_t package_id = get_package_id(resid); - const uint8_t type_id = get_type_id(resid); - const uint16_t entry_id = get_entry_id(resid); - - if (UNLIKELY(type_id == 0)) { - LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid); - return false; - } - - for (const auto& loaded_package : packages_) { - if (loaded_package->GetPackageId() == package_id) { - return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry); - } - } - return false; -} bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library) { diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index b033137b4764..ef08897d997a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -69,6 +69,8 @@ struct ResolvedBag { Entry entries[0]; }; +struct FindEntryResult; + // AssetManager2 is the main entry point for accessing assets and resources. // AssetManager2 provides caching of resources retrieved via the underlying ApkAssets. class AssetManager2 { @@ -127,7 +129,7 @@ class AssetManager2 { // If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap' // will be excluded from the list. std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false, - bool exclude_mipmap = false); + bool exclude_mipmap = false) const; // Returns all the locales for which there are resources defined. This includes resource // locales in all the ApkAssets set for this AssetManager. @@ -136,24 +138,24 @@ class AssetManager2 { // If `merge_equivalent_languages` is set to true, resource locales will be canonicalized // and de-duped in the resulting list. std::set<std::string> GetResourceLocales(bool exclude_system = false, - bool merge_equivalent_languages = false); + bool merge_equivalent_languages = false) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found located // in the assets/ directory. // `mode` controls how the file is opened. // // NOTE: The loaded APKs are searched in reverse order. - std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode); + std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const; // Opens a file within the assets/ directory of the APK specified by `cookie`. // `mode` controls how the file is opened. std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded. // The entries are sorted by their ASCII name. - std::unique_ptr<AssetDir> OpenDir(const std::string& dirname); + std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found. // `mode` controls how the file is opened. @@ -161,24 +163,24 @@ class AssetManager2 { // // NOTE: The loaded APKs are searched in reverse order. std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie = nullptr); + ApkAssetsCookie* out_cookie = nullptr) const; // Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened. // This is typically used to open a specific AndroidManifest.xml, or a binary XML file // referenced by a resource lookup with GetResource(). std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Populates the `out_name` parameter with resource name information. // Utf8 strings are preferred, and only if they are unavailable are // the Utf16 variants populated. // Returns false if the resource was not found or the name was missing/corrupt. - bool GetResourceName(uint32_t resid, ResourceName* out_name); + bool GetResourceName(uint32_t resid, ResourceName* out_name) const; // Populates `out_flags` with the bitmask of configuration axis that this resource varies with. // See ResTable_config for the list of configuration axis. // Returns false if the resource was not found. - bool GetResourceFlags(uint32_t resid, uint32_t* out_flags); + bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const; // Finds the resource ID assigned to `resource_name`. // `resource_name` must be of the form '[package:][type/]entry'. @@ -186,7 +188,7 @@ class AssetManager2 { // If no type is specified in `resource_name`, then `fallback_type` is used as the type. // Returns 0x0 if no resource by that name was found. uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {}, - const std::string& fallback_package = {}); + const std::string& fallback_package = {}) const; // Retrieves the best matching resource with ID `resid`. The resource value is filled into // `out_value` and the configuration for the selected value is populated in `out_selected_config`. @@ -199,7 +201,7 @@ class AssetManager2 { // this function logs if the resource was a map/bag type before returning kInvalidCookie. ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags); + uint32_t* out_flags) const; // Resolves the resource reference in `in_out_value` if the data type is // Res_value::TYPE_REFERENCE. @@ -215,7 +217,7 @@ class AssetManager2 { // it was not found. ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference); + uint32_t* out_last_reference) const; // Retrieves the best matching bag/map resource with ID `resid`. // This method will resolve all parent references for this bag and merge keys with the child. @@ -233,9 +235,9 @@ class AssetManager2 { std::unique_ptr<Theme> NewTheme(); template <typename Func> - void ForEachPackage(Func func) { + void ForEachPackage(Func func) const { for (const PackageGroup& package_group : package_groups_) { - func(package_group.packages_.front()->GetPackageName(), + func(package_group.packages_.front().loaded_package_->GetPackageName(), package_group.dynamic_ref_table.mAssignedPackageId); } } @@ -260,7 +262,7 @@ class AssetManager2 { // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds. ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match, - FindEntryResult* out_entry); + FindEntryResult* out_entry) const; // Assigns package IDs to all shared library ApkAssets. // Should be called whenever the ApkAssets are changed. @@ -270,13 +272,43 @@ class AssetManager2 { // bitmask `diff`. void InvalidateCaches(uint32_t diff); + // Triggers the re-construction of lists of types that match the set configuration. + // This should always be called when mutating the AssetManager's configuration or ApkAssets set. + void RebuildFilterList(); + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; + // A collection of configurations and their associated ResTable_type that match the current + // AssetManager configuration. + struct FilteredConfigGroup { + std::vector<ResTable_config> configurations; + std::vector<const ResTable_type*> types; + }; + + // Represents an single package. + struct ConfiguredPackage { + // A pointer to the immutable, loaded package info. + const LoadedPackage* loaded_package_; + + // A mutable AssetManager-specific list of configurations that match the AssetManager's + // current configuration. This is used as an optimization to avoid checking every single + // candidate configuration when looking up resources. + ByteBucketArray<FilteredConfigGroup> filtered_configs_; + }; + + // Represents a logical package, which can be made up of many individual packages. Each package + // in a PackageGroup shares the same package name and package ID. struct PackageGroup { - std::vector<const LoadedPackage*> packages_; + // The set of packages that make-up this group. + std::vector<ConfiguredPackage> packages_; + + // The cookies associated with each package in the group. They share the same order as + // packages_. std::vector<ApkAssetsCookie> cookies_; + + // A library reference table that contains build-package ID to runtime-package ID mappings. DynamicRefTable dynamic_ref_table; }; @@ -350,7 +382,7 @@ class Theme { ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config = nullptr, uint32_t* in_out_type_spec_flags = nullptr, - uint32_t* out_last_ref = nullptr); + uint32_t* out_last_ref = nullptr) const; private: DISALLOW_COPY_AND_ASSIGN(Theme); diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h index f281921824e7..03fad4947dfe 100644 --- a/libs/androidfw/include/androidfw/AttributeFinder.h +++ b/libs/androidfw/include/androidfw/AttributeFinder.h @@ -58,6 +58,7 @@ class BackTrackingAttributeFinder { BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end); Iterator Find(uint32_t attr); + inline Iterator end(); private: void JumpToClosestAttribute(uint32_t package_id); @@ -201,6 +202,11 @@ Iterator BackTrackingAttributeFinder<Derived, Iterator>::Find(uint32_t attr) { return end_; } +template <typename Derived, typename Iterator> +Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() { + return end_; +} + } // namespace android #endif // ANDROIDFW_ATTRIBUTE_FINDER_H diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h index 69b760414846..35ef98d8c704 100644 --- a/libs/androidfw/include/androidfw/AttributeResolution.h +++ b/libs/androidfw/include/androidfw/AttributeResolution.h @@ -17,7 +17,8 @@ #ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H #define ANDROIDFW_ATTRIBUTERESOLUTION_H -#include <androidfw/ResourceTypes.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceTypes.h" namespace android { @@ -42,19 +43,19 @@ enum { // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid, uint32_t* src_values, size_t src_values_length, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` is NOT optional and must NOT be nullptr. -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs, +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); } // namespace android diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 965e2dbd2fb2..35ae5fcd9e7b 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -41,32 +41,40 @@ class DynamicPackageEntry { int package_id = 0; }; -struct FindEntryResult { - // A pointer to the resource table entry for this resource. - // If the size of the entry is > sizeof(ResTable_entry), it can be cast to - // a ResTable_map_entry and processed as a bag/map. - const ResTable_entry* entry = nullptr; - - // The configuration for which the resulting entry was defined. - const ResTable_config* config = nullptr; - - // Stores the resulting bitmask of configuration axis with which the resource value varies. - uint32_t type_flags = 0u; - - // The dynamic package ID map for the package from which this resource came from. - const DynamicRefTable* dynamic_ref_table = nullptr; - - // The string pool reference to the type's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef type_string_ref; - - // The string pool reference to the entry's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef entry_string_ref; +// TypeSpec is going to be immediately proceeded by +// an array of Type structs, all in the same block of memory. +struct TypeSpec { + // Pointer to the mmapped data where flags are kept. + // Flags denote whether the resource entry is public + // and under which configurations it varies. + const ResTable_typeSpec* type_spec; + + // Pointer to the mmapped data where the IDMAP mappings for this type + // exist. May be nullptr if no IDMAP exists. + const IdmapEntry_header* idmap_entries; + + // The number of types that follow this struct. + // There is a type for each configuration that entries are defined for. + size_t type_count; + + // Trick to easily access a variable number of Type structs + // proceeding this struct, and to ensure their alignment. + const ResTable_type* types[0]; + + inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const { + if (entry_index >= dtohl(type_spec->entryCount)) { + return 0u; + } + + const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1); + return flags[entry_index]; + } }; -struct TypeSpec; -class LoadedArsc; +// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of +// ResTable_type pointers. +// TypeSpecPtr is a managed pointer that knows how to delete itself. +using TypeSpecPtr = util::unique_cptr<TypeSpec>; class LoadedPackage { public: @@ -76,9 +84,6 @@ class LoadedPackage { ~LoadedPackage(); - bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - FindEntryResult* out_entry) const; - // Finds the entry with the specified type name and entry name. The names are in UTF-16 because // the underlying ResStringPool API expects this. For now this is acceptable, but since // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. @@ -86,6 +91,12 @@ class LoadedPackage { // for patching the correct package ID to the resource ID. uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const; + static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index); + + static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index); + + static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset); + // Returns the string pool where type names are stored. inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; @@ -135,14 +146,32 @@ class LoadedPackage { // before being inserted into the set. This may cause some equivalent locales to de-dupe. void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const; + // type_idx is TT - 1 from 0xPPTTEEEE. + inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const { + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + return type_specs_[type_index - type_id_offset_].get(); + } + + template <typename Func> + void ForEachTypeSpec(Func f) const { + for (size_t i = 0; i < type_specs_.size(); i++) { + const TypeSpecPtr& ptr = type_specs_[i]; + if (ptr != nullptr) { + uint8_t type_id = ptr->type_spec->id; + if (ptr->idmap_entries != nullptr) { + type_id = ptr->idmap_entries->target_type_id; + } + f(ptr.get(), type_id - 1); + } + } + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); LoadedPackage(); - bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx, - const ResTable_config& config, FindEntryResult* out_entry) const; - ResStringPool type_string_pool_; ResStringPool key_string_pool_; std::string package_name_; @@ -152,7 +181,7 @@ class LoadedPackage { bool system_ = false; bool overlay_ = false; - ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_; + ByteBucketArray<TypeSpecPtr> type_specs_; std::vector<DynamicPackageEntry> dynamic_package_map_; }; @@ -180,25 +209,20 @@ class LoadedArsc { return &global_string_pool_; } - // Finds the resource with ID `resid` with the best value for configuration `config`. - // The parameter `out_entry` will be filled with the resulting resource entry. - // The resource entry can be a simple entry (ResTable_entry) or a complex bag - // (ResTable_entry_map). - bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const; + // Gets a pointer to the package with the specified package ID, or nullptr if no such package + // exists. + const LoadedPackage* GetPackageById(uint8_t package_id) const; - // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist. - const LoadedPackage* GetPackageForId(uint32_t resid) const; + // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. + inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { + return packages_; + } // Returns true if this is a system provided resource. inline bool IsSystem() const { return system_; } - // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. - inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { - return packages_; - } - private: DISALLOW_COPY_AND_ASSIGN(LoadedArsc); diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h new file mode 100644 index 000000000000..64924f433245 --- /dev/null +++ b/libs/androidfw/include/androidfw/MutexGuard.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ANDROIDFW_MUTEXGUARD_H +#define ANDROIDFW_MUTEXGUARD_H + +#include <mutex> +#include <type_traits> + +#include "android-base/macros.h" + +namespace android { + +template <typename T> +class ScopedLock; + +// Owns the guarded object and protects access to it via a mutex. +// The guarded object is inaccessible via this class. +// The mutex is locked and the object accessed via the ScopedLock<T> class. +// +// NOTE: The template parameter T should not be a raw pointer, since ownership +// is ambiguous and error-prone. Instead use an std::unique_ptr<>. +// +// Example use: +// +// Guarded<std::string> shared_string("hello"); +// { +// ScopedLock<std::string> locked_string(shared_string); +// *locked_string += " world"; +// } +// +template <typename T> +class Guarded { + static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer"); + + public: + explicit Guarded() : guarded_() { + } + + template <typename U = T> + explicit Guarded(const T& guarded, + typename std::enable_if<std::is_copy_constructible<U>::value>::type = void()) + : guarded_(guarded) { + } + + template <typename U = T> + explicit Guarded(T&& guarded, + typename std::enable_if<std::is_move_constructible<U>::value>::type = void()) + : guarded_(std::move(guarded)) { + } + + private: + friend class ScopedLock<T>; + + DISALLOW_COPY_AND_ASSIGN(Guarded); + + std::mutex lock_; + T guarded_; +}; + +template <typename T> +class ScopedLock { + public: + explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) { + } + + T& operator*() { + return guarded_; + } + + T* operator->() { + return &guarded_; + } + + T* get() { + return &guarded_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedLock); + + std::lock_guard<std::mutex> lock_; + T& guarded_; +}; + +} // namespace android + +#endif // ANDROIDFW_MUTEXGUARD_H diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index c2eae855bb7b..d94779bf5225 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,7 +28,7 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin StringPiece* out_entry); inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) { - return resid | (static_cast<uint32_t>(package_id) << 24); + return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24); } inline uint8_t get_package_id(uint32_t resid) { diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index 6c43a67e602f..e2b9f0040989 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -26,58 +26,56 @@ using ::android::base::unique_fd; using ::com::android::basic::R; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; namespace android { TEST(ApkAssetsTest, LoadApk) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - - const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000); - ASSERT_NE(nullptr, loaded_package); - - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkFromFd) { const std::string path = GetTestDataPath() + "/basic/basic.apk"; unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY)); - ASSERT_GE(fd.get(), 0); + ASSERT_THAT(fd.get(), Ge(0)); std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - - const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000); - ASSERT_NE(nullptr, loaded_package); - - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkAsSharedLibrary) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic()); loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic()); } @@ -86,19 +84,22 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) { ResTable target_table; const std::string target_path = GetTestDataPath() + "/basic/basic.apk"; ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/)); + ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); ResTable overlay_table; const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk"; ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/)); + ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); util::unique_cptr<void> idmap_data; void* temp_data; size_t idmap_len; - ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), - overlay_path.c_str(), &temp_data, &idmap_len)); + ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), + overlay_path.c_str(), &temp_data, &idmap_len), + Eq(NO_ERROR)); idmap_data.reset(temp_data); TemporaryFile tf; @@ -108,37 +109,30 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) { // Open something so that the destructor of TemporaryFile closes a valid fd. tf.fd = open("/dev/null", O_WRONLY); - std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path); - ASSERT_NE(nullptr, loaded_overlay_apk); + ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull()); } TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } } TEST(ApkAssetsTest, OpenUncompressedAssetFd) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(asset, NotNull()); off64_t start, length; unique_fd fd(asset->openFileDescriptor(&start, &length)); - EXPECT_GE(fd.get(), 0); + ASSERT_THAT(fd.get(), Ge(0)); lseek64(fd.get(), start, SEEK_SET); @@ -146,7 +140,7 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) { buffer.resize(length); ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length)); - EXPECT_EQ("This should be uncompressed.\n\n", buffer); + EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n")); } } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 85e8f25394e9..437e14772964 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -81,17 +81,18 @@ static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld); -static void BM_AssetManagerGetResource(benchmark::State& state) { - GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) { + GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state); } -BENCHMARK(BM_AssetManagerGetResource); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref); -static void BM_AssetManagerGetResourceOld(benchmark::State& state) { - GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) { + GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, + state); } -BENCHMARK(BM_AssetManagerGetResourceOld); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref); static void BM_AssetManagerGetLibraryResource(benchmark::State& state) { GetResourceBenchmark( @@ -196,7 +197,7 @@ BENCHMARK(BM_AssetManagerGetResourceLocales); static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { AssetManager assets; if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, - false /*isSystemAssets*/)) { + true /*isSystemAssets*/)) { state.SkipWithError("Failed to load assets"); return; } @@ -211,4 +212,44 @@ static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerGetResourceLocalesOld); +static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath); + if (apk == nullptr) { + state.SkipWithError("Failed to load assets"); + return; + } + + AssetManager2 assets; + assets.SetApkAssets({apk.get()}); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.SetConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFramework); + +static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) { + AssetManager assets; + if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, + true /*isSystemAssets*/)) { + state.SkipWithError("Failed to load assets"); + return; + } + + const ResTable& table = assets.getResources(true); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.setConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld); + } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 92462a6cfadf..eaf79cb12901 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -59,7 +59,7 @@ class AssetManager2Test : public ::testing::Test { libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk"); ASSERT_NE(nullptr, libclient_assets_); - appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); + appaslib_assets_ = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); ASSERT_NE(nullptr, appaslib_assets_); system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/); @@ -233,6 +233,25 @@ TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) { assetmanager.SetApkAssets( {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + const ResolvedBag* bag = assetmanager.GetBag(fix_package_id(lib_one::R::style::Theme, 0x03)); + ASSERT_NE(nullptr, bag); + ASSERT_GE(bag->entry_count, 2u); + + // First two attributes come from lib_one. + EXPECT_EQ(1, bag->entries[0].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[0].key)); + EXPECT_EQ(1, bag->entries[1].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); +} + +TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme); ASSERT_NE(nullptr, bag); ASSERT_GE(bag->entry_count, 2u); diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp new file mode 100644 index 000000000000..fa300c50218a --- /dev/null +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "benchmark/benchmark.h" + +//#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/AttributeResolution.h" +#include "androidfw/ResourceTypes.h" + +#include "BenchmarkHelpers.h" +#include "data/basic/R.h" +#include "data/styles/R.h" + +namespace app = com::android::app; +namespace basic = com::android::basic; + +namespace android { + +constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk"; +constexpr const static uint32_t Theme_Material_Light = 0x01030237u; + +static void BM_ApplyStyle(benchmark::State& state) { + std::unique_ptr<const ApkAssets> styles_apk = + ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + if (styles_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({styles_apk.get()}); + + std::unique_ptr<Asset> asset = + assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(app::R::style::StyleTwo); + + std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two, + app::R::attr::attr_three, app::R::attr::attr_four, + app::R::attr::attr_five, app::R::attr::attr_empty}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), + attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyle); + +static void BM_ApplyStyleFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath); + if (framework_apk == nullptr) { + state.SkipWithError("failed to load framework assets"); + return; + } + + std::unique_ptr<const ApkAssets> basic_apk = + ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); + if (basic_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()}); + + ResTable_config device_config; + memset(&device_config, 0, sizeof(device_config)); + device_config.language[0] = 'e'; + device_config.language[1] = 'n'; + device_config.country[0] = 'U'; + device_config.country[1] = 'S'; + device_config.orientation = ResTable_config::ORIENTATION_PORT; + device_config.smallestScreenWidthDp = 700; + device_config.screenWidthDp = 700; + device_config.screenHeightDp = 1024; + device_config.sdkVersion = 27; + + Res_value value; + ResTable_config config; + uint32_t flags = 0u; + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/, + 0u /*density_override*/, &value, &config, &flags); + if (cookie == kInvalidCookie) { + state.SkipWithError("failed to find R.layout.layout"); + return; + } + + size_t len = 0u; + const char* layout_path = + assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len); + if (layout_path == nullptr || len == 0u) { + state.SkipWithError("failed to lookup layout path"); + return; + } + + std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset( + StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(Theme_Material_Light); + + std::array<uint32_t, 92> attrs{ + {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099, + 0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f, + 0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151, + 0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158, + 0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f, + 0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166, + 0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d, + 0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d, + 0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5, + 0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f, + 0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d, + 0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df, + 0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9, + 0x011100ca}}; + + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/, + attrs.data(), attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyleFramework); + +} // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp index 2d73ce8f8ee3..c8dbe205fee2 100644 --- a/libs/androidfw/tests/AttributeResolution_test.cpp +++ b/libs/androidfw/tests/AttributeResolution_test.cpp @@ -21,6 +21,8 @@ #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/macros.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceUtils.h" #include "TestHelpers.h" #include "data/styles/R.h" @@ -32,15 +34,14 @@ namespace android { class AttributeResolutionTest : public ::testing::Test { public: virtual void SetUp() override { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString( - GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(), - 1 /*cookie*/, true /*copyData*/)); + styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, styles_assets_); + assetmanager_.SetApkAssets({styles_assets_.get()}); } protected: - ResTable table_; + std::unique_ptr<const ApkAssets> styles_assets_; + AssetManager2 assetmanager_; }; class AttributeResolutionXmlTest : public AttributeResolutionTest { @@ -48,13 +49,12 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { virtual void SetUp() override { AttributeResolutionTest::SetUp(); - std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", - "res/layout/layout.xml", &contents)); + std::unique_ptr<Asset> asset = + assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + ASSERT_NE(nullptr, asset); - ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(), - true /*copyData*/)); + ASSERT_EQ(NO_ERROR, + xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/)); // Skip to the first tag. while (xml_parser_.next() != ResXMLParser::START_TAG) { @@ -65,15 +65,50 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { ResXMLTree xml_parser_; }; +TEST(AttributeResolutionLibraryTest, ApplyStyleWithDefaultStyleResId) { + AssetManager2 assetmanager; + auto apk_assets = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, apk_assets); + assetmanager.SetApkAssets({apk_assets.get()}); + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + + std::array<uint32_t, 2> attrs{ + {fix_package_id(R::attr::attr_one, 0x02), fix_package_id(R::attr::attr_two, 0x02)}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + ApplyStyle(theme.get(), nullptr /*xml_parser*/, 0u /*def_style_attr*/, + fix_package_id(R::style::StyleOne, 0x02), attrs.data(), attrs.size(), values.data(), + indices.data()); + + const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; + + const uint32_t* values_cursor = values.data(); + EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]); + EXPECT_EQ(1u, values_cursor[STYLE_DATA]); + EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]); + EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]); + EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]); + EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]); + + values_cursor += STYLE_NUM_ENTRIES; + EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]); + EXPECT_EQ(2u, values_cursor[STYLE_DATA]); + EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]); + EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]); + EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]); + EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]); +} + TEST_F(AttributeResolutionTest, Theme) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/, + ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/, nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(), attrs.size(), values.data(), nullptr /*out_indices*/)); @@ -126,8 +161,8 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(), - nullptr /*out_indices*/)); + ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(), + values.data(), nullptr /*out_indices*/)); uint32_t* values_cursor = values.data(); EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]); @@ -171,15 +206,15 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { } TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; std::array<uint32_t, attrs.size() + 1> indices; - ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(), + ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), attrs.size(), values.data(), indices.data()); const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index 7149beef797f..faddfe599af4 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -33,19 +33,21 @@ void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTab } } + // Make sure to force creation of the ResTable first, or else the configuration doesn't get set. + const ResTable& table = assetmanager.getResources(true); if (config != nullptr) { assetmanager.setConfiguration(*config); } - const ResTable& table = assetmanager.getResources(true); - Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_ref = 0u; while (state.KeepRunning()) { - table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, - &selected_config); + ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, + &selected_config); + table.resolveReference(&value, block, &last_ref, &flags, &selected_config); } } @@ -72,10 +74,12 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_ Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_id = 0u; while (state.KeepRunning()) { - assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value, - &selected_config, &flags); + ApkAssetsCookie cookie = assetmanager.GetResource( + resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags); + assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id); } } diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 37ddafb14fd3..bedebd66cb2f 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -16,6 +16,8 @@ #include "androidfw/LoadedArsc.h" +#include "androidfw/ResourceUtils.h" + #include "TestHelpers.h" #include "data/basic/R.h" #include "data/libclient/R.h" @@ -27,6 +29,13 @@ namespace basic = com::android::basic; namespace libclient = com::android::libclient; namespace sparse = com::android::sparse; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; + namespace android { TEST(LoadedArscTest, LoadSinglePackageArsc) { @@ -35,39 +44,24 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); - - const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); - - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 24; - - FindEntryResult entry; + ASSERT_THAT(loaded_arsc, NotNull()); - ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry)); - ASSERT_NE(nullptr, entry.entry); -} + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one)); + ASSERT_THAT(package, NotNull()); + EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app")); + EXPECT_THAT(package->GetPackageId(), Eq(0x7f)); -TEST(LoadedArscTest, FindDefaultEntry) { - std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + const uint8_t type_index = get_type_id(app::R::string::string_one) - 1; + const uint16_t entry_index = get_entry_id(app::R::string::string_one); - std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - desired_config.language[0] = 'd'; - desired_config.language[1] = 'e'; - - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry)); - ASSERT_NE(nullptr, entry.entry); + const ResTable_type* type = type_spec->types[0]; + ASSERT_THAT(type, NotNull()); + ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull()); } TEST(LoadedArscTest, LoadSparseEntryApp) { @@ -76,15 +70,22 @@ TEST(LoadedArscTest, LoadSparseEntryApp) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9)); + ASSERT_THAT(package, NotNull()); - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; + const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1; + const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9); - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry)); - ASSERT_NE(nullptr, entry.entry); + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + + const ResTable_type* type = type_spec->types[0]; + ASSERT_THAT(type, NotNull()); + ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull()); } TEST(LoadedArscTest, LoadSharedLibrary) { @@ -93,14 +94,13 @@ TEST(LoadedArscTest, LoadSharedLibrary) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName()); - EXPECT_EQ(0, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); @@ -114,25 +114,23 @@ TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) { "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_FALSE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); // The library has two dependencies. - ASSERT_EQ(2u, dynamic_pkg_map.size()); + ASSERT_THAT(dynamic_pkg_map, SizeIs(2u)); + EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one")); + EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02)); - EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name); - EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id); - - EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name); - EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id); + EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two")); + EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03)); } TEST(LoadedArscTest, LoadAppAsSharedLibrary) { @@ -143,13 +141,12 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) { std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/, true /*load_as_shared_library*/); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); } TEST(LoadedArscTest, LoadFeatureSplit) { @@ -157,21 +154,27 @@ TEST(LoadedArscTest, LoadFeatureSplit) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3)); + ASSERT_THAT(package, NotNull()); - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry)); + uint8_t type_index = get_type_id(basic::R::string::test3) - 1; + uint8_t entry_index = get_entry_id(basic::R::string::test3); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); size_t len; - const char16_t* type_name16 = entry.type_string_ref.string16(&len); - ASSERT_NE(nullptr, type_name16); - ASSERT_NE(0u, len); + const char16_t* type_name16 = + package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len); + ASSERT_THAT(type_name16, NotNull()); + EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string")); - std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len)); - EXPECT_EQ(std::string("string"), type_name); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull()); } class MockLoadedIdmap : public LoadedIdmap { @@ -199,23 +202,33 @@ class MockLoadedIdmap : public LoadedIdmap { }; TEST(LoadedArscTest, LoadOverlay) { - std::string contents, overlay_contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc", - &overlay_contents)); + &contents)); MockLoadedIdmap loaded_idmap; std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap); - ASSERT_NE(nullptr, loaded_arsc); - - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry)); + LoadedArsc::Load(StringPiece(contents), &loaded_idmap); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u); + ASSERT_THAT(package, NotNull()); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); + + // The entry being overlaid doesn't exist at the original entry index. + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull()); + + // Since this is an overlay, the actual entry ID must be mapped. + ASSERT_THAT(type_spec->idmap_entries, NotNull()); + uint16_t target_entry_id = 0u; + ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id)); + ASSERT_THAT(target_entry_id, Eq(0x0u)); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull()); } // structs with size fields (like Res_value, ResTable_entry) should be diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h index 43a995536d89..df0c642f4565 100644 --- a/libs/androidfw/tests/TestHelpers.h +++ b/libs/androidfw/tests/TestHelpers.h @@ -20,6 +20,7 @@ #include <string> #include "androidfw/ResourceTypes.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "CommonHelpers.h" diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h index 94a2a14ced87..b7e814fea079 100644 --- a/libs/androidfw/tests/data/basic/R.h +++ b/libs/androidfw/tests/data/basic/R.h @@ -34,6 +34,7 @@ struct R { struct layout { enum : uint32_t { main = 0x7f020000, + layoutt = 0x7f020001, }; }; @@ -55,6 +56,7 @@ struct R { number2 = 0x7f040001, ref1 = 0x7f040002, ref2 = 0x7f040003, + deep_ref = 0x7f040004, // From feature number3 = 0x80030000, diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk Binary files differindex 18ef75e91ded..1733b6a16546 100644 --- a/libs/androidfw/tests/data/basic/basic.apk +++ b/libs/androidfw/tests/data/basic/basic.apk diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml new file mode 100644 index 000000000000..045ede454bca --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/ok" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginStart="2dip" + android:layout_marginEnd="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:text="@android:string/ok" />
\ No newline at end of file diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml index 6c474596b5cd..b3435629265b 100644 --- a/libs/androidfw/tests/data/basic/res/values/values.xml +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -22,6 +22,7 @@ <attr name="attr2" format="reference|integer" /> <public type="layout" name="main" id="0x7f020000" /> + <public type="layout" name="layout" id="0x7f020001" /> <public type="string" name="test1" id="0x7f030000" /> <string name="test1">test1</string> @@ -43,6 +44,18 @@ <public type="integer" name="ref2" id="0x7f040003" /> <integer name="ref2">12000</integer> + <public type="integer" name="deep_ref" id="0x7f040004" /> + <integer name="deep_ref">@integer/deep_ref_1</integer> + <integer name="deep_ref_1">@integer/deep_ref_2</integer> + <integer name="deep_ref_2">@integer/deep_ref_3</integer> + <integer name="deep_ref_3">@integer/deep_ref_4</integer> + <integer name="deep_ref_4">@integer/deep_ref_5</integer> + <integer name="deep_ref_5">@integer/deep_ref_6</integer> + <integer name="deep_ref_6">@integer/deep_ref_7</integer> + <integer name="deep_ref_7">@integer/deep_ref_8</integer> + <integer name="deep_ref_8">@integer/deep_ref_9</integer> + <integer name="deep_ref_9">100</integer> + <public type="style" name="Theme1" id="0x7f050000" /> <style name="Theme1"> <item name="com.android.basic:attr1">100</item> diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index b2edd3392873..9e0d10d07892 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -187,11 +187,6 @@ void SkiaCanvas::restoreToCount(int restoreCount) { static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) { SkCanvas::SaveLayerFlags layerFlags = 0; - // We intentionally ignore the SaveFlags::HasAlphaLayer and - // SkCanvas::kIsOpaque_SaveLayerFlag flags because HWUI ignores it - // and our Android client may use it incorrectly. - // In Skia, this flag is purely for performance optimization. - if (!(flags & SaveFlags::ClipToLayer)) { layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag; } diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp index 06e2d6c00ef5..fc009d871620 100644 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -204,10 +204,6 @@ static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) { saveFlags |= SaveFlags::ClipToLayer; } - if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) { - saveFlags |= SaveFlags::HasAlphaLayer; - } - return saveFlags; } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index c33dce1777bb..d3c6eddd02eb 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -236,6 +236,62 @@ public class LocationManager { */ public static final String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown"; + /** + * Broadcast intent action for Settings app to inject a footer at the bottom of location + * settings. + * + * <p>This broadcast is used for two things: + * <ol> + * <li>For receivers to inject a footer with provided text. This is for use only by apps + * that are included in the system image. </li> + * <li>For receivers to know their footer is injected under location settings.</li> + * </ol> + * + * <p>To inject a footer to location settings, you must declare a broadcast receiver of + * {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} in the manifest as so: + * <pre> + * <receiver android:name="com.example.android.footer.MyFooterInjector"> + * <intent-filter> + * <action android:name="com.android.settings.location.INJECT_FOOTER" /> + * </intent-filter> + * <meta-data + * android:name="com.android.settings.location.FOOTER_STRING" + * android:resource="@string/my_injected_footer_string" /> + * </receiver> + * </pre> + * + * <p>On entering location settings, Settings app will send a + * {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast to receivers whose footer is successfully + * injected. On leaving location settings, the footer becomes not visible to users. Settings app + * will send a {@link #SETTINGS_FOOTER_REMOVED_ACTION} broadcast to those receivers. + * + * @hide + */ + public static final String SETTINGS_FOOTER_DISPLAYED_ACTION = + "com.android.settings.location.DISPLAYED_FOOTER"; + + /** + * Broadcast intent action when location settings footer is not visible to users. + * + * <p>See {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} for more detail on how to use. + * + * @hide + */ + public static final String SETTINGS_FOOTER_REMOVED_ACTION = + "com.android.settings.location.REMOVED_FOOTER"; + + /** + * Metadata name for {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast + * receivers to specify a string resource id as location settings footer text. This is for use + * only by apps that are included in the system image. + * + * <p>See {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} for more detail on how to use. + * + * @hide + */ + public static final String METADATA_SETTINGS_FOOTER_STRING = + "com.android.settings.location.FOOTER_STRING"; + // Map from LocationListeners to their associated ListenerTransport objects private HashMap<LocationListener,ListenerTransport> mListeners = new HashMap<LocationListener,ListenerTransport>(); diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java index 5d0c8e234d40..5467a69ea0bb 100644 --- a/media/java/android/media/AudioFocusInfo.java +++ b/media/java/android/media/AudioFocusInfo.java @@ -38,6 +38,10 @@ public final class AudioFocusInfo implements Parcelable { private int mLossReceived; private int mFlags; + // generation count for the validity of a request/response async exchange between + // external focus policy and MediaFocusControl + private long mGenCount = -1; + /** * Class constructor @@ -61,6 +65,16 @@ public final class AudioFocusInfo implements Parcelable { mSdkTarget = sdk; } + /** @hide */ + public void setGen(long g) { + mGenCount = g; + } + + /** @hide */ + public long getGen() { + return mGenCount; + } + /** * The audio attributes for the audio focus request. @@ -128,6 +142,7 @@ public final class AudioFocusInfo implements Parcelable { dest.writeInt(mLossReceived); dest.writeInt(mFlags); dest.writeInt(mSdkTarget); + dest.writeLong(mGenCount); } @Override @@ -168,6 +183,8 @@ public final class AudioFocusInfo implements Parcelable { if (mSdkTarget != other.mSdkTarget) { return false; } + // mGenCount is not used to verify equality between two focus holds as multiple requests + // (hence of different generations) could correspond to the same hold return true; } @@ -175,7 +192,7 @@ public final class AudioFocusInfo implements Parcelable { = new Parcelable.Creator<AudioFocusInfo>() { public AudioFocusInfo createFromParcel(Parcel in) { - return new AudioFocusInfo( + final AudioFocusInfo afi = new AudioFocusInfo( AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa in.readInt(), // int clientUid in.readString(), //String clientId @@ -185,6 +202,8 @@ public final class AudioFocusInfo implements Parcelable { in.readInt(), //int flags in.readInt() //int sdkTarget ); + afi.setGen(in.readLong()); + return afi; } public AudioFocusInfo[] newArray(int size) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index bf51d97f6b2f..0be54ec2b2d9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -32,6 +32,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.audiopolicy.AudioPolicy; +import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; @@ -54,10 +55,13 @@ import android.util.Log; import android.util.Slog; import android.view.KeyEvent; +import com.android.internal.annotations.GuardedBy; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -2338,6 +2342,20 @@ public class AudioManager { } } } + + @Override + public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) { + synchronized (mFocusRequestsLock) { + // TODO use generation counter as the key instead + final BlockingFocusResultReceiver focusReceiver = + mFocusRequestsAwaitingResult.remove(clientId); + if (focusReceiver != null) { + focusReceiver.notifyResult(requestResult); + } else { + Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver"); + } + } + } }; private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) { @@ -2390,6 +2408,40 @@ public class AudioManager { */ public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; + /** @hide */ + @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = { + AUDIOFOCUS_REQUEST_FAILED, + AUDIOFOCUS_REQUEST_GRANTED, + AUDIOFOCUS_REQUEST_DELAYED } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface FocusRequestResult {} + + /** + * @hide + * code returned when a synchronous focus request on the client-side is to be blocked + * until the external audio focus policy decides on the response for the client + */ + public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100; + + /** + * Timeout duration in ms when waiting on an external focus policy for the result for a + * focus request + */ + private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200; + + private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id"; + + private final Object mFocusRequestsLock = new Object(); + /** + * Map of all receivers of focus request results, one per unresolved focus request. + * Receivers are added before sending the request to the external focus policy, + * and are removed either after receiving the result, or after the timeout. + * This variable is lazily initialized. + */ + @GuardedBy("mFocusRequestsLock") + private HashMap<String, BlockingFocusResultReceiver> mFocusRequestsAwaitingResult; + /** * Request audio focus. @@ -2656,18 +2708,100 @@ public class AudioManager { // some tests don't have a Context sdk = Build.VERSION.SDK_INT; } - try { - status = service.requestAudioFocus(afr.getAudioAttributes(), - afr.getFocusGain(), mICallBack, - mAudioFocusDispatcher, - getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()), - getContext().getOpPackageName() /* package name */, afr.getFlags(), - ap != null ? ap.cb() : null, - sdk); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + + final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()); + final BlockingFocusResultReceiver focusReceiver; + synchronized (mFocusRequestsLock) { + try { + // TODO status contains result and generation counter for ext policy + status = service.requestAudioFocus(afr.getAudioAttributes(), + afr.getFocusGain(), mICallBack, + mAudioFocusDispatcher, + clientId, + getContext().getOpPackageName() /* package name */, afr.getFlags(), + ap != null ? ap.cb() : null, + sdk); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + if (mFocusRequestsAwaitingResult == null) { + mFocusRequestsAwaitingResult = + new HashMap<String, BlockingFocusResultReceiver>(1); + } + focusReceiver = new BlockingFocusResultReceiver(clientId); + mFocusRequestsAwaitingResult.put(clientId, focusReceiver); + } + focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS); + if (DEBUG && !focusReceiver.receivedResult()) { + Log.e(TAG, "requestAudio response from ext policy timed out, denying request"); + } + synchronized (mFocusRequestsLock) { + mFocusRequestsAwaitingResult.remove(clientId); + } + return focusReceiver.requestResult(); + } + + // helper class that abstracts out the handling of spurious wakeups in Object.wait() + private static final class SafeWaitObject { + private boolean mQuit = false; + + public void safeNotify() { + synchronized (this) { + mQuit = true; + this.notify(); + } + } + + public void safeWait(long millis) throws InterruptedException { + final long timeOutTime = java.lang.System.currentTimeMillis() + millis; + synchronized (this) { + while (!mQuit) { + final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis(); + if (timeToWait < 0) { break; } + this.wait(timeToWait); + } + } + } + } + + private static final class BlockingFocusResultReceiver { + private final SafeWaitObject mLock = new SafeWaitObject(); + @GuardedBy("mLock") + private boolean mResultReceived = false; + // request denied by default (e.g. timeout) + private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED; + private final String mFocusClientId; + + BlockingFocusResultReceiver(String clientId) { + mFocusClientId = clientId; + } + + boolean receivedResult() { return mResultReceived; } + int requestResult() { return mFocusRequestResult; } + + void notifyResult(int requestResult) { + synchronized (mLock) { + mResultReceived = true; + mFocusRequestResult = requestResult; + mLock.safeNotify(); + } + } + + public void waitForResult(long timeOutMs) { + synchronized (mLock) { + if (mResultReceived) { + // the result was received before waiting + return; + } + try { + mLock.safeWait(timeOutMs); + } catch (InterruptedException e) { } + } } - return status; } /** @@ -2714,6 +2848,32 @@ public class AudioManager { /** * @hide + * Set the result to the audio focus request received through + * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}. + * @param afi the information about the focus requester + * @param requestResult the result to the focus request to be passed to the requester + * @param ap a valid registered {@link AudioPolicy} configured as a focus policy. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setFocusRequestResult(@NonNull AudioFocusInfo afi, + @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (ap == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy"); + } + final IAudioService service = getService(); + try { + service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * Notifies an application with a focus listener of gain or loss of audio focus. * This method can only be used by owners of an {@link AudioPolicy} configured with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true. diff --git a/media/java/android/media/IAudioFocusDispatcher.aidl b/media/java/android/media/IAudioFocusDispatcher.aidl index 09575f733e32..3b33c5b7a46a 100644 --- a/media/java/android/media/IAudioFocusDispatcher.aidl +++ b/media/java/android/media/IAudioFocusDispatcher.aidl @@ -25,4 +25,6 @@ oneway interface IAudioFocusDispatcher { void dispatchAudioFocusChange(int focusChange, String clientId); + void dispatchFocusResultFromExtPolicy(int requestResult, String clientId); + } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 88d0a6088026..cd4143cf3120 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -207,5 +207,8 @@ interface IAudioService { int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent); + oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult, + in IAudioPolicyCallback pcb); + // WARNING: read warning at top of file, it is recommended to add new methods at the end } diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java index bbcce82f2e2d..bd6667faff31 100644 --- a/media/java/android/media/VolumePolicy.java +++ b/media/java/android/media/VolumePolicy.java @@ -23,7 +23,7 @@ import java.util.Objects; /** @hide */ public final class VolumePolicy implements Parcelable { - public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, true, 400); + public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, false, 400); /** * Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 4de731a9a8a3..219063564132 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -463,9 +463,9 @@ public class AudioPolicy { * Only ever called if the {@link AudioPolicy} was built with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. * @param afi information about the focus request and the requester - * @param requestResult the result that was returned synchronously by the framework to the - * application, {@link #AUDIOFOCUS_REQUEST_FAILED},or - * {@link #AUDIOFOCUS_REQUEST_DELAYED}. + * @param requestResult deprecated after the addition of + * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)} + * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}. */ public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {} /** @@ -534,7 +534,7 @@ public class AudioPolicy { sendMsg(MSG_FOCUS_REQUEST, afi, requestResult); if (DEBUG) { Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client=" - + afi.getClientId() + "reqRes=" + requestResult); + + afi.getClientId() + " gen=" + afi.getGen()); } } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 7e5f581dd1c6..fe2f64fa2d96 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -127,11 +127,11 @@ cc_library_shared { "liblzma", "libmedia", "libmedia_helper", - "libmedia_player2", "libmedia_player2_util", "libmediadrm", "libmediaextractor", "libmediametrics", + "libmediaplayer2", "libmediautils", "libnativehelper", "libnetd_client", diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 27eaed05b04d..0eb98f34b6bb 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -21,15 +21,14 @@ #include <sys/stat.h> -#include <media/mediaplayer2.h> #include <media/AudioResamplerPublic.h> #include <media/DataSourceDesc.h> #include <media/MediaHTTPService.h> -#include <media/MediaPlayer2Interface.h> #include <media/MediaAnalyticsItem.h> #include <media/NdkWrapper.h> #include <media/stagefright/Utils.h> #include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition +#include <mediaplayer2/mediaplayer2.h> #include <stdio.h> #include <assert.h> #include <limits.h> diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp index 98e9a42d944d..e70d5ea0d566 100644 --- a/native/android/asset_manager.cpp +++ b/native/android/asset_manager.cpp @@ -18,9 +18,11 @@ #include <utils/Log.h> #include <android/asset_manager_jni.h> +#include <android_runtime/android_util_AssetManager.h> #include <androidfw/Asset.h> #include <androidfw/AssetDir.h> #include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include <utils/threads.h> #include "jni.h" @@ -35,21 +37,20 @@ using namespace android; // ----- struct AAssetDir { - AssetDir* mAssetDir; + std::unique_ptr<AssetDir> mAssetDir; size_t mCurFileIndex; String8 mCachedFileName; - explicit AAssetDir(AssetDir* dir) : mAssetDir(dir), mCurFileIndex(0) { } - ~AAssetDir() { delete mAssetDir; } + explicit AAssetDir(std::unique_ptr<AssetDir> dir) : + mAssetDir(std::move(dir)), mCurFileIndex(0) { } }; // ----- struct AAsset { - Asset* mAsset; + std::unique_ptr<Asset> mAsset; - explicit AAsset(Asset* asset) : mAsset(asset) { } - ~AAsset() { delete mAsset; } + explicit AAsset(std::unique_ptr<Asset> asset) : mAsset(std::move(asset)) { } }; // -------------------- Public native C API -------------------- @@ -104,19 +105,18 @@ AAsset* AAssetManager_open(AAssetManager* amgr, const char* filename, int mode) return NULL; } - AssetManager* mgr = static_cast<AssetManager*>(amgr); - Asset* asset = mgr->open(filename, amMode); - if (asset == NULL) { - return NULL; + ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr)); + std::unique_ptr<Asset> asset = locked_mgr->Open(filename, amMode); + if (asset == nullptr) { + return nullptr; } - - return new AAsset(asset); + return new AAsset(std::move(asset)); } AAssetDir* AAssetManager_openDir(AAssetManager* amgr, const char* dirName) { - AssetManager* mgr = static_cast<AssetManager*>(amgr); - return new AAssetDir(mgr->openDir(dirName)); + ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr)); + return new AAssetDir(locked_mgr->OpenDir(dirName)); } /** diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml index 72be35ba3594..15dd64beb4cf 100644 --- a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml +++ b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml @@ -20,6 +20,8 @@ <option name="test-suite-tag" value="apct" /> <option name="test-tag" value="PrintSpoolerOutOfProcessTests" /> + <option name="config-descriptor:metadata" key="component" value="print" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.printspooler.outofprocess.tests" /> <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index 741db3441699..3c2ca2d31888 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -16,11 +16,11 @@ ifeq ($(LOCAL_USE_AAPT2),true) LOCAL_STATIC_JAVA_LIBRARIES += \ android-support-annotations \ - android-arch-lifecycle-common + apptoolkit-lifecycle-common LOCAL_STATIC_ANDROID_LIBRARIES += \ android-support-v4 \ - android-arch-lifecycle-runtime \ + apptoolkit-lifecycle-runtime \ android-support-v7-recyclerview \ android-support-v7-preference \ android-support-v7-appcompat \ diff --git a/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml b/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml index ac56a2d9d2c3..bc330c7356b5 100644 --- a/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml +++ b/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml @@ -22,25 +22,40 @@ android:fillViewport ="true" android:orientation="vertical"> - <com.android.settingslib.notification.ZenRadioLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/zen_conditions" + <LinearLayout + android:id="@+id/dialog_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginEnd="4dp" - android:layout_marginStart="4dp" - android:paddingBottom="4dp" - android:orientation="horizontal"> - <RadioGroup - android:id="@+id/zen_radio_buttons" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - <LinearLayout - android:id="@+id/zen_radio_buttons_content" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical"/> - </com.android.settingslib.notification.ZenRadioLayout> + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.android.settingslib.notification.ZenRadioLayout + android:id="@+id/zen_conditions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginEnd="4dp" + android:layout_marginStart="4dp" + android:paddingBottom="4dp" + android:orientation="horizontal"> + <RadioGroup + android:id="@+id/zen_radio_buttons" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + <LinearLayout + android:id="@+id/zen_radio_buttons_content" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"/> + </com.android.settingslib.notification.ZenRadioLayout> + + <TextView + android:id="@+id/zen_alarm_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="18dp" + android:layout_marginEnd="16dp" + android:textDirection="locale" + android:textColor="?android:attr/colorError"/> + </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 486a9bbed1f2..6ef3facc1aea 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1039,5 +1039,13 @@ <string name="zen_interruption_level_priority">Priority only</string> <!-- [CHAR LIMIT=20] Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. --> <string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string> + <!-- Warning text when an alarm might be silenced by Do Not Disturb [CHAR LIMIT=NONE] --> + <string name="zen_alarm_warning_indef">You won\'t hear your next alarm <xliff:g id="when" example="at 7:00 AM">%1$s</xliff:g> unless you turn this off before then</string> + <!-- Warning text when an alarm might be silenced by Do Not Disturb due to a time-based condition [CHAR LIMIT=NONE] --> + <string name="zen_alarm_warning">You won\'t hear your next alarm <xliff:g id="when" example="at 7:00 AM">%1$s</xliff:g></string> + <!-- Alarm template for near alarms [CHAR LIMIT=25] --> + <string name="alarm_template">at <xliff:g id="when" example="7:00 AM">%1$s</xliff:g></string> + <!-- Alarm template for far in the future alarms [CHAR LIMIT=25] --> + <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java index cda4e454fe74..5f7ba586fbad 100755..100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -194,8 +194,13 @@ public class LocalBluetoothAdapter { return mState; } - synchronized void setBluetoothStateInt(int state) { - mState = state; + void setBluetoothStateInt(int state) { + synchronized(this) { + if (mState == state) { + return; + } + mState = state; + } if (state == BluetoothAdapter.STATE_ON) { // if mProfileManager hasn't been constructed yet, it will diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index a20f68753a78..1a54d6a3396b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -1,5 +1,3 @@ -package com.android.settingslib.notification; - /* * Copyright (C) 2018 The Android Open Source Project * @@ -16,6 +14,8 @@ package com.android.settingslib.notification; * limitations under the License. */ +package com.android.settingslib.notification; + import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlertDialog; @@ -28,6 +28,7 @@ import android.provider.Settings; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; import android.text.TextUtils; +import android.text.format.DateFormat; import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; @@ -40,6 +41,7 @@ import android.widget.RadioGroup; import android.widget.ScrollView; import android.widget.TextView; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.policy.PhoneWindow; @@ -48,11 +50,11 @@ import com.android.settingslib.R; import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.Locale; import java.util.Objects; public class EnableZenModeDialog { - - private static final String TAG = "QSEnableZenModeDialog"; + private static final String TAG = "EnableZenModeDialog"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; @@ -60,25 +62,37 @@ public class EnableZenModeDialog { private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1]; private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); - private static final int FOREVER_CONDITION_INDEX = 0; - private static final int COUNTDOWN_CONDITION_INDEX = 1; - private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2; + @VisibleForTesting + protected static final int FOREVER_CONDITION_INDEX = 0; + @VisibleForTesting + protected static final int COUNTDOWN_CONDITION_INDEX = 1; + @VisibleForTesting + protected static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2; private static final int SECONDS_MS = 1000; private static final int MINUTES_MS = 60 * SECONDS_MS; - private Uri mForeverId; + @VisibleForTesting + protected Uri mForeverId; private int mBucketIndex = -1; private AlarmManager mAlarmManager; private int mUserId; private boolean mAttached; - private Context mContext; + @VisibleForTesting + protected Context mContext; + @VisibleForTesting + protected TextView mZenAlarmWarning; + @VisibleForTesting + protected LinearLayout mZenRadioGroupContent; + private RadioGroup mZenRadioGroup; - private LinearLayout mZenRadioGroupContent; private int MAX_MANUAL_DND_OPTIONS = 3; + @VisibleForTesting + protected LayoutInflater mLayoutInflater; + public EnableZenModeDialog(Context context) { mContext = context; } @@ -133,32 +147,40 @@ public class EnableZenModeDialog { for (int i = 0; i < N; i++) { mZenRadioGroupContent.getChildAt(i).setVisibility(View.GONE); } + + mZenAlarmWarning.setVisibility(View.GONE); } protected View getContentView() { - final LayoutInflater inflater = new PhoneWindow(mContext).getLayoutInflater(); - View contentView = inflater.inflate(R.layout.zen_mode_turn_on_dialog_container, null); + if (mLayoutInflater == null) { + mLayoutInflater = new PhoneWindow(mContext).getLayoutInflater(); + } + View contentView = mLayoutInflater.inflate(R.layout.zen_mode_turn_on_dialog_container, + null); ScrollView container = (ScrollView) contentView.findViewById(R.id.container); mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons); mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content); + mZenAlarmWarning = container.findViewById(R.id.zen_alarm_warning); for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) { - final View radioButton = inflater.inflate(R.layout.zen_mode_radio_button, + final View radioButton = mLayoutInflater.inflate(R.layout.zen_mode_radio_button, mZenRadioGroup, false); mZenRadioGroup.addView(radioButton); radioButton.setId(i); - final View radioButtonContent = inflater.inflate(R.layout.zen_mode_condition, + final View radioButtonContent = mLayoutInflater.inflate(R.layout.zen_mode_condition, mZenRadioGroupContent, false); radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS); mZenRadioGroupContent.addView(radioButtonContent); } + hideAllConditions(); return contentView; } - private void bind(final Condition condition, final View row, final int rowId) { + @VisibleForTesting + protected void bind(final Condition condition, final View row, final int rowId) { if (condition == null) throw new IllegalArgumentException("condition must not be null"); final boolean enabled = condition.state == Condition.STATE_TRUE; final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() : @@ -181,6 +203,7 @@ public class EnableZenModeDialog { if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId); MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT); + updateAlarmWarningText(tag.condition); announceConditionSelection(tag); } } @@ -190,11 +213,13 @@ public class EnableZenModeDialog { row.setVisibility(View.VISIBLE); } - private ConditionTag getConditionTagAt(int index) { + @VisibleForTesting + protected ConditionTag getConditionTagAt(int index) { return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag(); } - private void bindConditions(Condition c) { + @VisibleForTesting + protected void bindConditions(Condition c) { // forever bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX), FOREVER_CONDITION_INDEX); @@ -236,11 +261,13 @@ public class EnableZenModeDialog { return info != null ? info.getTriggerTime() : 0; } - private boolean isAlarm(Condition c) { + @VisibleForTesting + protected boolean isAlarm(Condition c) { return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id); } - private boolean isCountdown(Condition c) { + @VisibleForTesting + protected boolean isCountdown(Condition c) { return c != null && ZenModeConfig.isValidCountdownConditionId(c.id); } @@ -264,7 +291,8 @@ public class EnableZenModeDialog { } // Returns a time condition if the next alarm is within the next week. - private Condition getTimeUntilNextAlarmCondition() { + @VisibleForTesting + protected Condition getTimeUntilNextAlarmCondition() { GregorianCalendar weekRange = new GregorianCalendar(); setToMidnight(weekRange); weekRange.add(Calendar.DATE, 6); @@ -282,7 +310,8 @@ public class EnableZenModeDialog { return null; } - private void bindGenericCountdown() { + @VisibleForTesting + protected void bindGenericCountdown() { mBucketIndex = DEFAULT_BUCKET_INDEX; Condition countdown = ZenModeConfig.toTimeCondition(mContext, MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); @@ -366,7 +395,8 @@ public class EnableZenModeDialog { } } - private void bindNextAlarm(Condition c) { + @VisibleForTesting + protected void bindNextAlarm(Condition c) { View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX); ConditionTag tag = (ConditionTag) alarmContent.getTag(); @@ -415,6 +445,7 @@ public class EnableZenModeDialog { MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); } bind(newCondition, row, rowId); + updateAlarmWarningText(tag.condition); tag.rb.setChecked(true); announceConditionSelection(tag); } @@ -428,8 +459,43 @@ public class EnableZenModeDialog { } } + private void updateAlarmWarningText(Condition condition) { + String warningText = computeAlarmWarningText(condition); + mZenAlarmWarning.setText(warningText); + mZenAlarmWarning.setVisibility(warningText == null ? View.GONE : View.VISIBLE); + } + + private String computeAlarmWarningText(Condition condition) { + final long now = System.currentTimeMillis(); + final long nextAlarm = getNextAlarm(); + if (nextAlarm < now) { + return null; + } + int warningRes = 0; + if (condition == null || isForever(condition)) { + warningRes = R.string.zen_alarm_warning_indef; + } else { + final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id); + if (time > now && nextAlarm < time) { + warningRes = R.string.zen_alarm_warning; + } + } + if (warningRes == 0) { + return null; + } + final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000; + final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser()); + final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma"); + final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); + final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm); + final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far; + final String template = mContext.getResources().getString(templateRes, formattedTime); + return mContext.getResources().getString(warningRes, template); + } + // used as the view tag on condition rows - private static class ConditionTag { + @VisibleForTesting + protected static class ConditionTag { public RadioButton rb; public View lines; public TextView line1; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 109eb97ed2d8..8115ede2729e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -41,7 +41,7 @@ import com.android.settingslib.TwoTargetPreference; import com.android.settingslib.Utils; import com.android.settingslib.wifi.AccessPoint.Speed; -public class AccessPointPreference extends TwoTargetPreference { +public class AccessPointPreference extends Preference { private static final int[] STATE_SECURED = { R.attr.state_encrypted @@ -115,6 +115,7 @@ public class AccessPointPreference extends TwoTargetPreference { int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld, int level, IconInjector iconInjector) { super(context); + setWidgetLayoutResource(R.layout.access_point_friction_widget); mBadgeCache = cache; mAccessPoint = accessPoint; mForSavedNetworks = forSavedNetworks; @@ -153,20 +154,6 @@ public class AccessPointPreference extends TwoTargetPreference { ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon); bindFrictionImage(frictionImageView); - setDividerVisibility(view, View.GONE); - } - - protected void setDividerVisibility(final PreferenceViewHolder view, - @View.Visibility int visibility) { - final View divider = view.findViewById(R.id.two_target_divider); - if (divider != null) { - divider.setVisibility(visibility); - } - } - - @Override - protected int getSecondTargetResId() { - return R.layout.access_point_friction_widget; } protected void updateIcon(int level, Context context) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java new file mode 100644 index 000000000000..777cd98e2ef7 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java @@ -0,0 +1,149 @@ +/* + * 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.settingslib.notification; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.net.Uri; +import android.service.notification.Condition; +import android.view.LayoutInflater; + +import com.android.settingslib.TestConfig; +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class EnableZenModeDialogTest { + private EnableZenModeDialog mController; + + @Mock + private Context mContext; + @Mock + private Fragment mFragment; + + private Context mShadowContext; + private LayoutInflater mLayoutInflater; + private Condition mCountdownCondition; + private Condition mAlarmCondition; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mShadowContext = RuntimeEnvironment.application; + when(mContext.getApplicationContext()).thenReturn(mContext); + when(mFragment.getContext()).thenReturn(mShadowContext); + mLayoutInflater = LayoutInflater.from(mShadowContext); + + mController = spy(new EnableZenModeDialog(mContext)); + mController.mContext = mContext; + mController.mLayoutInflater = mLayoutInflater; + mController.mForeverId = Condition.newId(mContext).appendPath("forever").build(); + when(mContext.getString(com.android.internal.R.string.zen_mode_forever)) + .thenReturn("testSummary"); + mController.getContentView(); + + // these methods use static calls to ZenModeConfig which would normally fail in robotests, + // so instead do nothing: + doNothing().when(mController).bindGenericCountdown(); + doReturn(null).when(mController).getTimeUntilNextAlarmCondition(); + doReturn(0L).when(mController).getNextAlarm(); + doNothing().when(mController).bindNextAlarm(any()); + + // as a result of doing nothing above, must bind manually: + Uri alarm = Condition.newId(mContext).appendPath("alarm").build(); + mAlarmCondition = new Condition(alarm, "alarm", "", "", 0, 0, 0); + Uri countdown = Condition.newId(mContext).appendPath("countdown").build(); + mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0); + mController.bind(mCountdownCondition, + mController.mZenRadioGroupContent.getChildAt( + EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX), + EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX); + mController.bind(mAlarmCondition, + mController.mZenRadioGroupContent.getChildAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX), + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX); + } + + @Test + public void testForeverChecked() { + mController.bindConditions(mController.forever()); + + assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testNoneChecked() { + mController.bindConditions(null); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testAlarmChecked() { + doReturn(false).when(mController).isCountdown(mAlarmCondition); + doReturn(true).when(mController).isAlarm(mAlarmCondition); + + mController.bindConditions(mAlarmCondition); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertTrue(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testCountdownChecked() { + doReturn(false).when(mController).isAlarm(mCountdownCondition); + doReturn(true).when(mController).isCountdown(mCountdownCondition); + + mController.bindConditions(mCountdownCondition); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } +}
\ No newline at end of file diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 1dc8e46a694a..cfcfb95b0f88 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -43,7 +43,6 @@ import android.provider.Settings.Secure; import android.text.TextUtils; import android.util.Log; -import com.android.ims.ImsConfig; import com.android.internal.content.PackageHelper; import com.android.internal.telephony.Phone; import com.android.internal.telephony.RILConstants; @@ -1504,7 +1503,7 @@ class DatabaseHelper extends SQLiteOpenHelper { try { stmt = db.compileStatement("INSERT OR REPLACE INTO global(name,value)" + " VALUES(?,?);"); - loadStringSetting(stmt, Settings.Global.WIRELESS_CHARGING_STARTED_SOUND, + loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND, R.string.def_wireless_charging_started_sound); db.setTransactionSuccessful(); } finally { @@ -2578,7 +2577,7 @@ class DatabaseHelper extends SQLiteOpenHelper { R.string.def_car_dock_sound); loadStringSetting(stmt, Settings.Global.CAR_UNDOCK_SOUND, R.string.def_car_undock_sound); - loadStringSetting(stmt, Settings.Global.WIRELESS_CHARGING_STARTED_SOUND, + loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND, R.string.def_wireless_charging_started_sound); loadIntegerSetting(stmt, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 537e8dca39c8..39f2b5293dac 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -160,7 +160,7 @@ class SettingsProtoDumpUtil { Settings.Global.POWER_SOUNDS_ENABLED, GlobalSettingsProto.POWER_SOUNDS_ENABLED); dumpSetting(s, p, - Settings.Global.WIRELESS_CHARGING_STARTED_SOUND, + Settings.Global.CHARGING_STARTED_SOUND, GlobalSettingsProto.WIRELESS_CHARGING_STARTED_SOUND); dumpSetting(s, p, Settings.Global.CHARGING_SOUNDS_ENABLED, diff --git a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml b/packages/SystemUI/res/layout/recents_onboarding.xml index b3d5c9008039..12f278a204c7 100644 --- a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml +++ b/packages/SystemUI/res/layout/recents_onboarding.xml @@ -30,7 +30,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:text="@string/recents_swipe_up_onboarding" android:textColor="@android:color/white" android:textSize="16sp" android:drawableBottom="@drawable/ic_chevron_up"/> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index f9aa8216ec11..2d30f4c0082d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -47,7 +47,6 @@ <item type="id" name="qs_icon_tag"/> <item type="id" name="qs_slash_tag"/> <item type="id" name="scrim"/> - <item type="id" name="scrim_blanking"/> <item type="id" name="scrim_target"/> <item type="id" name="scrim_alpha_start"/> <item type="id" name="scrim_alpha_end"/> @@ -58,6 +57,8 @@ <item type="id" name="notification_plugin"/> <item type="id" name="transformation_start_x_tag"/> <item type="id" name="transformation_start_y_tag"/> + <item type="id" name="transformation_start_actual_width"/> + <item type="id" name="transformation_start_actual_height"/> <item type="id" name="transformation_start_scale_x_tag"/> <item type="id" name="transformation_start_scale_y_tag"/> <item type="id" name="continuous_clipping_tag"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index fadcbcd4f4bc..fc5ea4581a01 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -842,8 +842,6 @@ <string name="recents_stack_action_button_label">Clear all</string> <!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] --> <string name="recents_drag_hint_message">Drag here to use split screen</string> - <!-- Recents: Text that shows above the nav bar after launching a few apps. [CHAR LIMIT=NONE] --> - <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string> <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] --> <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 98ede4eafbe2..4cf817e02fff 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -39,4 +39,9 @@ interface ISystemUiProxy { * Called when the overview service has started the recents animation. */ void onRecentsAnimationStarted(); + + /** + * Specifies the text to be shown for onboarding the new swipe-up gesture to access recents. + */ + void setRecentsOnboardingText(CharSequence text); } diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index b7e1d67a5b3a..b30b0c575415 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -66,6 +66,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; + private CharSequence mOnboardingText; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -105,6 +106,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Binder.restoreCallingIdentity(token); } } + + public void setRecentsOnboardingText(CharSequence text) { + mOnboardingText = text; + } }; private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() { @@ -223,8 +228,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return mOverviewProxy; } - public ComponentName getLauncherComponent() { - return mLauncherComponentName; + public CharSequence getOnboardingText() { + return mOnboardingText; } private void disconnectFromLauncherService() { diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 9319bc60f9ef..adb4e33d1a19 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -49,7 +49,7 @@ public final class Prefs { Key.QS_NIGHTDISPLAY_ADDED, Key.SEEN_MULTI_USER, Key.NUM_APPS_LAUNCHED, - Key.HAS_SWIPED_UP_FOR_RECENTS, + Key.HAS_SEEN_RECENTS_ONBOARDING, }) public @interface Key { @Deprecated @@ -78,7 +78,7 @@ public final class Prefs { String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded"; String SEEN_MULTI_USER = "HasSeenMultiUser"; String NUM_APPS_LAUNCHED = "NumAppsLaunched"; - String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents"; + String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java index 9cda75c858e8..a44fd9a0f918 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java @@ -51,6 +51,23 @@ public class QSScrollLayout extends NestedScrollView { addView(linearLayout); } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (canScrollVertically(1) || canScrollVertically(-1)) { + return super.onInterceptTouchEvent(ev); + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (canScrollVertically(1) || canScrollVertically(-1)) { + return super.onTouchEvent(ev); + } + return false; + } + public boolean shouldIntercept(MotionEvent ev) { if (ev.getY() > (getBottom() - mFooterHeight)) { // Do not intercept touches that are below the divider between QS and the footer. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index ea6e174d786e..3597929229e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -21,6 +21,7 @@ import android.app.ActivityManager; import android.content.Intent; import android.provider.Settings; import android.service.quicksettings.Tile; +import android.support.annotation.StringRes; import android.widget.Switch; import com.android.internal.app.ColorDisplayController; @@ -30,6 +31,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.tileimpl.QSTileImpl; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class NightDisplayTile extends QSTileImpl<BooleanState> @@ -39,7 +41,9 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the * nearest hour and add on the AM/PM indicator. */ - private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h a"; + private static final String HOUR_MINUTE_DATE_TIME_PATTERN = "h a"; + private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h:m a"; + private ColorDisplayController mController; private boolean mIsListening; @@ -110,17 +114,26 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> case ColorDisplayController.AUTO_MODE_CUSTOM: // User-specified time, approximated to the nearest hour. - return isNightLightActivated - ? mContext.getString( - R.string.quick_settings_night_secondary_label_until, - mController.getCustomEndTime().format( - DateTimeFormatter.ofPattern( - APPROXIMATE_HOUR_DATE_TIME_PATTERN))) - : mContext.getString( - R.string.quick_settings_night_secondary_label_on_at, - mController.getCustomStartTime().format( - DateTimeFormatter.ofPattern( - APPROXIMATE_HOUR_DATE_TIME_PATTERN))); + final @StringRes int toggleTimeStringRes; + final LocalTime toggleTime; + final DateTimeFormatter toggleTimeFormat; + + if (isNightLightActivated) { + toggleTime = mController.getCustomEndTime(); + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_until; + } else { + toggleTime = mController.getCustomStartTime(); + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at; + } + + // Choose between just showing the hour or also showing the minutes (based on the + // user-selected toggle time). This helps reduce how much space the label takes. + toggleTimeFormat = DateTimeFormatter.ofPattern( + toggleTime.getMinute() == 0 + ? HOUR_MINUTE_DATE_TIME_PATTERN + : APPROXIMATE_HOUR_DATE_TIME_PATTERN); + + return mContext.getString(toggleTimeStringRes, toggleTime.format(toggleTimeFormat)); default: // No secondary label when auto mode is disabled. diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java index b2472bf73874..0ff8b085f035 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java @@ -16,13 +16,11 @@ package com.android.systemui.recents; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import android.annotation.TargetApi; import android.app.ActivityManager; -import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -31,6 +29,8 @@ import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.RippleDrawable; import android.os.Build; +import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -41,6 +41,7 @@ import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; +import com.android.systemui.OverviewProxyService; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; @@ -50,9 +51,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; * Shows onboarding for the new recents interaction in P (codenamed quickstep). */ @TargetApi(Build.VERSION_CODES.P) -public class SwipeUpOnboarding { +public class RecentsOnboarding { - private static final String TAG = "SwipeUpOnboarding"; + private static final String TAG = "RecentsOnboarding"; private static final boolean RESET_PREFS_FOR_DEBUG = false; private static final long SHOW_DELAY_MS = 500; private static final long SHOW_HIDE_DURATION_MS = 300; @@ -61,6 +62,7 @@ public class SwipeUpOnboarding { private final Context mContext; private final WindowManager mWindowManager; + private final OverviewProxyService mOverviewProxyService; private final View mLayout; private final TextView mTextView; private final ImageView mDismissView; @@ -113,11 +115,12 @@ public class SwipeUpOnboarding { } }; - public SwipeUpOnboarding(Context context) { + public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) { mContext = context; + mOverviewProxyService = overviewProxyService; final Resources res = context.getResources(); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null); + mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null); mTextView = mLayout.findViewById(R.id.onboarding_text); mDismissView = mLayout.findViewById(R.id.dismiss); mDarkBackgroundColor = res.getColor(android.R.color.background_dark); @@ -135,25 +138,25 @@ public class SwipeUpOnboarding { mDismissView.setOnClickListener(v -> hide(true)); if (RESET_PREFS_FOR_DEBUG) { - Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0); } } public void onConnectedToLauncher() { - boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext, - Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); - if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) { + boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) { ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener); mTaskListenerRegistered = true; } } public void onRecentsAnimationStarted() { - boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext, - Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); - if (!alreadyLearnedSwipeUpForRecents) { - Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true); + boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + if (!alreadySeenRecentsOnboarding) { + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true); onDisconnectedFromLauncher(); } } @@ -173,6 +176,12 @@ public class SwipeUpOnboarding { } public void show() { + CharSequence onboardingText = mOverviewProxyService.getOnboardingText(); + if (TextUtils.isEmpty(onboardingText)) { + Log.w(TAG, "Unable to get onboarding text"); + return; + } + mTextView.setText(onboardingText); // Only show in portrait. int orientation = mContext.getResources().getConfiguration().orientation; if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) { @@ -239,7 +248,7 @@ public class SwipeUpOnboarding { flags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; - lp.setTitle("SwipeUpOnboarding"); + lp.setTitle("RecentsOnboarding"); lp.gravity = Gravity.BOTTOM; return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java index 8227b7797706..d3a325d3b91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java @@ -21,6 +21,8 @@ import android.util.Pools; import android.view.View; import android.widget.ImageView; +import com.android.internal.widget.MessagingImageMessage; +import com.android.internal.widget.MessagingMessage; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -117,13 +119,15 @@ public class ImageTransformState extends TransformState { @Override protected boolean transformScale(TransformState otherState) { - return true; + return sameAs(otherState); } @Override public void recycle() { super.recycle(); - sInstancePool.release(this); + if (getClass() == ImageTransformState.class) { + sInstancePool.release(this); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java new file mode 100644 index 000000000000..b97995dd5f93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java @@ -0,0 +1,134 @@ +/* + * 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.systemui.statusbar.notification; + +import android.util.Pools; +import android.view.View; + +import com.android.internal.widget.MessagingImageMessage; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.statusbar.ViewTransformationHelper; + +/** + * A transform state of a image view. +*/ +public class MessagingImageTransformState extends ImageTransformState { + private static Pools.SimplePool<MessagingImageTransformState> sInstancePool + = new Pools.SimplePool<>(40); + private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width; + private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height; + private MessagingImageMessage mImageMessage; + + @Override + public void initFrom(View view, TransformInfo transformInfo) { + super.initFrom(view, transformInfo); + mImageMessage = (MessagingImageMessage) view; + } + + @Override + protected boolean sameAs(TransformState otherState) { + if (super.sameAs(otherState)) { + return true; + } + if (otherState instanceof MessagingImageTransformState) { + MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState; + return mImageMessage.sameAs(otherMessage.mImageMessage); + } + return false; + } + + public static MessagingImageTransformState obtain() { + MessagingImageTransformState instance = sInstancePool.acquire(); + if (instance != null) { + return instance; + } + return new MessagingImageTransformState(); + } + + @Override + protected boolean transformScale(TransformState otherState) { + return false; + } + + @Override + protected void transformViewFrom(TransformState otherState, int transformationFlags, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + super.transformViewFrom(otherState, transformationFlags, customTransformation, + transformationAmount); + float interpolatedValue = mDefaultInterpolator.getInterpolation( + transformationAmount); + if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) { + MessagingImageMessage otherMessage + = ((MessagingImageTransformState) otherState).mImageMessage; + if (transformationAmount == 0.0f) { + setStartActualWidth(otherMessage.getActualWidth()); + setStartActualHeight(otherMessage.getActualHeight()); + } + float startActualWidth = getStartActualWidth(); + mImageMessage.setActualWidth( + (int) NotificationUtils.interpolate(startActualWidth, + mImageMessage.getStaticWidth(), + interpolatedValue)); + float startActualHeight = getStartActualHeight(); + mImageMessage.setActualHeight( + (int) NotificationUtils.interpolate(startActualHeight, + mImageMessage.getHeight(), + interpolatedValue)); + } + } + + public int getStartActualWidth() { + Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH); + return tag == null ? -1 : (int) tag; + } + + public void setStartActualWidth(int actualWidth) { + mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth); + } + + public int getStartActualHeight() { + Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT); + return tag == null ? -1 : (int) tag; + } + + public void setStartActualHeight(int actualWidth) { + mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth); + } + + @Override + public void recycle() { + super.recycle(); + if (getClass() == MessagingImageTransformState.class) { + sInstancePool.release(this); + } + } + + @Override + protected void resetTransformedView() { + super.resetTransformedView(); + mImageMessage.setActualWidth(mImageMessage.getStaticWidth()); + mImageMessage.setActualHeight(mImageMessage.getHeight()); + } + + @Override + protected void reset() { + super.reset(); + mImageMessage = null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index 113118a1c8c5..314a31d336fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -22,6 +22,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.widget.MessagingGroup; +import com.android.internal.widget.MessagingImageMessage; import com.android.internal.widget.MessagingLayout; import com.android.internal.widget.MessagingLinearLayout; import com.android.internal.widget.MessagingMessage; @@ -30,6 +31,7 @@ import com.android.systemui.Interpolators; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * A transform state of the action list @@ -156,6 +158,7 @@ public class MessagingLayoutTransformState extends TransformState { } appear(ownGroup.getAvatar(), transformationAmount); appear(ownGroup.getSenderView(), transformationAmount); + appear(ownGroup.getIsolatedMessage(), transformationAmount); setClippingDeactivated(ownGroup.getSenderView(), true); setClippingDeactivated(ownGroup.getAvatar(), true); } @@ -187,12 +190,13 @@ public class MessagingLayoutTransformState extends TransformState { } disappear(ownGroup.getAvatar(), transformationAmount); disappear(ownGroup.getSenderView(), transformationAmount); + disappear(ownGroup.getIsolatedMessage(), transformationAmount); setClippingDeactivated(ownGroup.getSenderView(), true); setClippingDeactivated(ownGroup.getAvatar(), true); } private void appear(View child, float transformationAmount) { - if (child.getVisibility() == View.GONE) { + if (child == null || child.getVisibility() == View.GONE) { return; } TransformState ownState = TransformState.createFrom(child, mTransformInfo); @@ -201,7 +205,7 @@ public class MessagingLayoutTransformState extends TransformState { } private void disappear(View child, float transformationAmount) { - if (child.getVisibility() == View.GONE) { + if (child == null || child.getVisibility() == View.GONE) { return; } TransformState ownState = TransformState.createFrom(child, mTransformInfo); @@ -224,22 +228,24 @@ public class MessagingLayoutTransformState extends TransformState { private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup, float transformationAmount, boolean to) { + boolean useLinearTransformation = + otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), - true /* sameAsAny */); + true /* sameAsAny */, useLinearTransformation); transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), - true /* sameAsAny */); - MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); - MessagingLinearLayout otherMessages = otherGroup.getMessageContainer(); + true /* sameAsAny */, useLinearTransformation); + List<MessagingMessage> ownMessages = ownGroup.getMessages(); + List<MessagingMessage> otherMessages = otherGroup.getMessages(); float previousTranslation = 0; - for (int i = 0; i < ownMessages.getChildCount(); i++) { - View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i); + for (int i = 0; i < ownMessages.size(); i++) { + View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); if (isGone(child)) { continue; } - int otherIndex = otherMessages.getChildCount() - 1 - i; + int otherIndex = otherMessages.size() - 1 - i; View otherChild = null; if (otherIndex >= 0) { - otherChild = otherMessages.getChildAt(otherIndex); + otherChild = otherMessages.get(otherIndex).getView(); if (isGone(otherChild)) { otherChild = null; } @@ -252,7 +258,12 @@ public class MessagingLayoutTransformState extends TransformState { transformationAmount = 1.0f - transformationAmount; } } - transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */); + transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */ + useLinearTransformation); + if (transformationAmount == 0.0f + && otherGroup.getIsolatedMessage() == otherChild) { + ownGroup.setTransformingImages(true); + } if (otherChild == null) { child.setTranslationY(previousTranslation); setClippingDeactivated(child, true); @@ -264,12 +275,13 @@ public class MessagingLayoutTransformState extends TransformState { previousTranslation = child.getTranslationY(); } } + ownGroup.updateClipRect(); } private void transformView(float transformationAmount, boolean to, View ownView, - View otherView, boolean sameAsAny) { + View otherView, boolean sameAsAny, boolean useLinearTransformation) { TransformState ownState = TransformState.createFrom(ownView, mTransformInfo); - if (!mTransformInfo.isAnimating()) { + if (useLinearTransformation) { ownState.setDefaultInterpolator(Interpolators.LINEAR); } ownState.setIsSameAsAnyView(sameAsAny); @@ -339,11 +351,15 @@ public class MessagingLayoutTransformState extends TransformState { if (!isGone(ownGroup)) { MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); for (int j = 0; j < ownMessages.getChildCount(); j++) { - MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j); + View child = ownMessages.getChildAt(j); setVisible(child, visible, force); } setVisible(ownGroup.getAvatar(), visible, force); setVisible(ownGroup.getSenderView(), visible, force); + MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + setVisible(isolatedMessage, visible, force); + } } } } @@ -375,11 +391,17 @@ public class MessagingLayoutTransformState extends TransformState { } resetTransformedView(ownGroup.getAvatar()); resetTransformedView(ownGroup.getSenderView()); + MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + resetTransformedView(isolatedMessage); + } setClippingDeactivated(ownGroup.getAvatar(), false); setClippingDeactivated(ownGroup.getSenderView(), false); ownGroup.setTranslationY(0); ownGroup.getMessageContainer().setTranslationY(0); } + ownGroup.setTransformingImages(false); + ownGroup.updateClipRect(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 918b6edc0c30..fc8ceb6620a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -26,6 +26,7 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.internal.widget.MessagingImageMessage; import com.android.internal.widget.MessagingPropertyAnimator; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Interpolators; @@ -80,7 +81,7 @@ public class TransformState { private boolean mSameAsAny; private float mTransformationEndY = UNDEFINED; private float mTransformationEndX = UNDEFINED; - private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN; + protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN; public void initFrom(View view, TransformInfo transformInfo) { mTransformedView = view; @@ -131,7 +132,7 @@ public class TransformState { transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount); } - private void transformViewFrom(TransformState otherState, int transformationFlags, + protected void transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount) { final View transformedView = mTransformedView; @@ -449,6 +450,11 @@ public class TransformState { result.initFrom(view, transformInfo); return result; } + if (view instanceof MessagingImageMessage) { + MessagingImageTransformState result = MessagingImageTransformState.obtain(); + result.initFrom(view, transformInfo); + return result; + } if (view instanceof ImageView) { ImageTransformState result = ImageTransformState.obtain(); result.initFrom(view, transformInfo); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index 0d36efda9ece..7db6e2848b2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -157,6 +157,20 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture return (mDockWindowEnabled && interceptDockWindowEvent(event)); } + public boolean onTouchEvent(MotionEvent event) { + // The same down event was just sent on intercept and therefore can be ignored here + boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN + && mOverviewEventSender.getProxy() != null; + boolean result = mStatusBar.isPresenterFullyCollapsed() + && (mQuickScrubController.onTouchEvent(event) + || ignoreProxyDownEvent + || proxyMotionEvents(event)); + if (mDockWindowEnabled) { + result |= handleDockWindowEvent(event); + } + return result; + } + public void onDraw(Canvas canvas) { if (mOverviewEventSender.getProxy() != null) { mQuickScrubController.onDraw(canvas); @@ -307,20 +321,6 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture return DRAG_MODE_RECENTS; } - public boolean onTouchEvent(MotionEvent event) { - // The same down event was just sent on intercept and therefore can be ignored here - boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN - && mOverviewEventSender.getProxy() != null; - boolean result = mStatusBar.isPresenterFullyCollapsed() - && (mQuickScrubController.onTouchEvent(event) - || ignoreProxyDownEvent - || proxyMotionEvents(event)); - if (mDockWindowEnabled) { - result |= handleDockWindowEvent(event); - } - return result; - } - @Override public void onTuningChanged(String key, String newValue) { switch (key) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index c37dd55cc6ff..ad5144e941ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -50,14 +50,13 @@ import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.DockedStackExistsListener; import com.android.systemui.OverviewProxyService; -import com.android.systemui.OverviewProxyService.OverviewProxyListener; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.phone.NavGesture; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; -import com.android.systemui.recents.SwipeUpOnboarding; +import com.android.systemui.recents.RecentsOnboarding; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.policy.DeadZone; import com.android.systemui.statusbar.policy.KeyButtonDrawable; @@ -126,7 +125,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private NavigationBarInflaterView mNavigationInflaterView; private RecentsComponent mRecentsComponent; private Divider mDivider; - private SwipeUpOnboarding mSwipeUpOnboarding; + private RecentsOnboarding mRecentsOnboarding; private NotificationPanelView mPanelView; private class NavTransitionListener implements TransitionListener { @@ -236,7 +235,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav new ButtonDispatcher(R.id.rotate_suggestion)); mOverviewProxyService = Dependency.get(OverviewProxyService.class); - mSwipeUpOnboarding = new SwipeUpOnboarding(context); + mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService); } public BarTransitions getBarTransitions() { @@ -265,8 +264,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public void setRecentsAnimationStarted(boolean started) { mRecentsAnimationStarted = started; - if (mSwipeUpOnboarding != null) { - mSwipeUpOnboarding.onRecentsAnimationStarted(); + if (mRecentsOnboarding != null) { + mRecentsOnboarding.onRecentsAnimationStarted(); } } @@ -277,14 +276,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } @Override - public boolean onTouchEvent(MotionEvent event) { - if (mGestureHelper.onTouchEvent(event)) { - return true; - } - return mRecentsAnimationStarted || super.onTouchEvent(event); - } - - @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { @@ -300,9 +291,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } } + return mGestureHelper.onInterceptTouchEvent(event) || mRecentsAnimationStarted; } + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mGestureHelper.onTouchEvent(event)) { + return true; + } + return mRecentsAnimationStarted || super.onTouchEvent(event); + } + public void abortCurrentGesture() { getHomeButton().abortCurrentGesture(); } @@ -674,8 +674,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper != null) { mGestureHelper.onDarkIntensityChange(intensity); } - if (mSwipeUpOnboarding != null) { - mSwipeUpOnboarding.setContentDarkIntensity(intensity); + if (mRecentsOnboarding != null) { + mRecentsOnboarding.setContentDarkIntensity(intensity); } } @@ -793,7 +793,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateTaskSwitchHelper(); updateIcons(getContext(), mConfiguration, newConfig); updateRecentsIcon(); - mSwipeUpOnboarding.onConfigurationChanged(newConfig); + mRecentsOnboarding.onConfigurationChanged(newConfig); if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) { // If car mode or density changes, we need to reset the icons. @@ -897,9 +897,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { if (connectedToOverviewProxy) { - mSwipeUpOnboarding.onConnectedToLauncher(); + mRecentsOnboarding.onConnectedToLauncher(); } else { - mSwipeUpOnboarding.onDisconnectedFromLauncher(); + mRecentsOnboarding.onDisconnectedFromLauncher(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java index 001a1a2d7292..6bfaaf49b65b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java @@ -158,8 +158,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene } catch (RemoteException e) { Log.e(TAG, "Failed to send start of quick switch.", e); } + return true; } - return true; + return false; } }; @@ -189,6 +190,10 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene mNavigationBarView = navigationBarView; } + /** + * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying + * the event to the overview service. + */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); @@ -197,7 +202,10 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene homeButton.setDelayTouchFeedback(false); return false; } - mGestureDetector.onTouchEvent(event); + if (mGestureDetector.onTouchEvent(event)) { + // If the fling has been handled, then skip proxying the UP + return true; + } int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { @@ -240,8 +248,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene offset = pos - mTrackRect.left; trackSize = mTrackRect.width(); } - // Do not start scrubbing when dragging in the perpendicular direction - if (!mDraggingActive && exceededPerpendicularTouchSlop) { + // Do not start scrubbing when dragging in the perpendicular direction if we + // haven't already started quickscrub + if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) { mHandler.removeCallbacksAndMessages(null); return false; } @@ -295,6 +304,22 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene return mDraggingActive || mQuickScrubActive; } + /** + * @return true if we want to handle touch events for quick scrub/switch and prevent proxying + * the event to the overview service. + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mGestureDetector.onTouchEvent(event)) { + // If the fling has been handled, then skip proxying the UP + return true; + } + if (event.getAction() == MotionEvent.ACTION_UP) { + endQuickScrub(); + } + return mDraggingActive || mQuickScrubActive; + } + @Override public void onDraw(Canvas canvas) { int color = (int) mTrackColorEvaluator.evaluate(mDarkIntensity, mLightTrackColor, @@ -341,14 +366,6 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene } @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - endQuickScrub(); - } - return false; - } - - @Override public void setBarState(boolean isVertical, boolean isRTL) { mIsVertical = isVertical; mIsRTL = isRTL; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 44e0c257ef24..2b16e740d0ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.os.Trace; import android.util.Log; import android.util.MathUtils; +import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -111,7 +112,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; static final int TAG_KEY_ANIM = R.id.scrim; - static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking; private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target; private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; @@ -166,6 +166,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mScreenBlankingCallbackCalled; private Callback mCallback; private boolean mWallpaperSupportsAmbientMode; + private Choreographer.FrameCallback mPendingFrameCallback; private final WakeLock mWakeLock; private boolean mWakeLockHeld; @@ -248,6 +249,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mCurrentInFrontAlpha = state.getFrontAlpha(); mCurrentBehindAlpha = state.getBehindAlpha(); + if (mPendingFrameCallback != null) { + Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback); + mPendingFrameCallback = null; + } + // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary // to do the same when you're just showing the brightness mirror. mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; @@ -276,13 +282,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mWallpaperVisibilityTimedOut = false; } - if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { - scheduleUpdate(); - } else { + if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { // In case the user isn't unlocked, make sure to delay a bit because the system is hosed // with too many things at this case, in order to not skip the initial frames. mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; + } else if (!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) { + // Execute first frame immediately when display was completely off. + // Scheduling a frame isn't enough because the system may aggressively enter doze, + // delaying callbacks or never triggering them until the power button is pressed. + onPreDraw(); + } else { + scheduleUpdate(); } } @@ -687,11 +698,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } } - final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null; - if (mBlankScreen || blankingInProgress) { - if (!blankingInProgress) { - blankDisplay(); - } + if (mPendingFrameCallback != null) { + // Display is off and we're waiting. + return; + } else if (mBlankScreen) { + // Need to blank the display before continuing. + blankDisplay(); return; } else if (!mScreenBlankingCallbackCalled) { // Not blanking the screen. Letting the callback know that we're ready @@ -745,45 +757,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void blankDisplay() { - final float initialAlpha = mScrimInFront.getViewAlpha(); - final int initialTint = mScrimInFront.getTint(); - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - anim.addUpdateListener(animation -> { - final float amount = (float) animation.getAnimatedValue(); - float animAlpha = MathUtils.lerp(initialAlpha, 1, amount); - int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount); - updateScrimColor(mScrimInFront, animAlpha, animTint); - dispatchScrimsVisible(); - }); - anim.setInterpolator(getInterpolator()); - anim.setDuration(mDozeParameters.getPulseInDuration()); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCallback != null) { - mCallback.onDisplayBlanked(); - mScreenBlankingCallbackCalled = true; - } - Runnable blankingCallback = () -> { - mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null); - mBlankScreen = false; - // Try again. - updateScrims(); - }; - - // Setting power states can happen after we push out the frame. Make sure we - // stay fully opaque until the power state request reaches the lower levels. - getHandler().postDelayed(blankingCallback, 100); + updateScrimColor(mScrimInFront, 1, Color.BLACK); + // Notify callback that the screen is completely black and we're + // ready to change the display power mode + mPendingFrameCallback = frameTimeNanos -> { + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; } - }); - anim.start(); - mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim); - // Finish animation if we're already at its final state - if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) { - anim.end(); - } + Runnable blankingCallback = () -> { + mPendingFrameCallback = null; + mBlankScreen = false; + // Try again. + updateScrims(); + }; + + // Setting power states can happen after we push out the frame. Make sure we + // stay fully opaque until the power state request reaches the lower levels. + getHandler().postDelayed(blankingCallback, 100); + }; + doOnTheNextFrame(mPendingFrameCallback); + } + + @VisibleForTesting + protected void doOnTheNextFrame(Choreographer.FrameCallback callback) { + Choreographer.getInstance().postFrameCallback(callback); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 314d6aa2bf57..381e4af31853 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -48,7 +48,6 @@ public enum ScrimState { // set our scrim to black in this frame to avoid flickering and // fade it out afterwards. mBlankScreen = true; - updateScrimColor(mScrimInFront, 1, Color.BLACK); } } else { mAnimationDuration = ScrimController.ANIMATION_DURATION; @@ -86,9 +85,6 @@ public enum ScrimState { AOD(3) { @Override public void prepare(ScrimState previousState) { - if (previousState == ScrimState.PULSING && !mCanControlScreenOff) { - updateScrimColor(mScrimInFront, 1, Color.BLACK); - } final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); final boolean wasPulsing = previousState == ScrimState.PULSING; mBlankScreen = wasPulsing && !mCanControlScreenOff; @@ -115,9 +111,6 @@ public enum ScrimState { && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f; mCurrentBehindTint = Color.BLACK; mBlankScreen = mDisplayRequiresBlanking; - if (mDisplayRequiresBlanking) { - updateScrimColor(mScrimInFront, 1, Color.BLACK); - } } }, diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java index 5c888ac89c15..6ed07f8d2c37 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java @@ -49,6 +49,8 @@ import android.util.Pair; import android.view.Window; import android.view.WindowManager; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.Dependency; @@ -81,6 +83,7 @@ public class OutputChooserDialog extends Dialog private final MediaRouterWrapper mRouter; private final MediaRouterCallback mRouterCallback; private long mLastUpdateTime; + static final boolean INCLUDE_MEDIA_ROUTES = false; private boolean mIsInCall; protected boolean isAttached; @@ -174,7 +177,7 @@ public class OutputChooserDialog extends Dialog public void onAttachedToWindow() { super.onAttachedToWindow(); - if (!mIsInCall) { + if (!mIsInCall && INCLUDE_MEDIA_ROUTES) { mRouter.addCallback(mRouteSelector, mRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); } @@ -201,6 +204,7 @@ public class OutputChooserDialog extends Dialog @Override public void show() { super.show(); + Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER); mHardwareLayout.setTranslationX(getAnimTranslation()); mHardwareLayout.setAlpha(0); mHardwareLayout.animate() @@ -214,6 +218,7 @@ public class OutputChooserDialog extends Dialog @Override public void dismiss() { + Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER); mHardwareLayout.setTranslationX(0); mHardwareLayout.setAlpha(1); mHardwareLayout.animate() @@ -236,11 +241,15 @@ public class OutputChooserDialog extends Dialog if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) { final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT); mBluetoothController.connect(device); } } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) { final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag; if (route.isEnabled()) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT); route.select(); } } @@ -251,8 +260,12 @@ public class OutputChooserDialog extends Dialog if (item == null || item.tag == null) return; if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) { final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT); mBluetoothController.disconnect(device); } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT); mRouter.unselect(UNSELECT_REASON_DISCONNECTED); } } @@ -272,7 +285,7 @@ public class OutputChooserDialog extends Dialog addBluetoothDevices(items); // Add remote displays - if (!mIsInCall) { + if (!mIsInCall && INCLUDE_MEDIA_ROUTES) { addRemoteDisplayRoutes(items); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index efa8386802e2..0203c43d3683 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -27,16 +27,15 @@ import android.os.Handler; import android.view.WindowManager.LayoutParams; import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionController.Extension; import com.android.systemui.tuner.TunerService; import java.io.FileDescriptor; @@ -53,8 +52,8 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb"; public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false; - public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true; - public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true; + public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false; + public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false; private final SystemUI mSysui; private final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index a131a618512f..c622677ceb7e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -396,13 +396,9 @@ public class VolumeDialogImpl implements VolumeDialog { if (hasVibrator) { mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); } else { - final boolean wasZero = ss.level == 0; - mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0); mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); } } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { - final boolean wasZero = ss.level == 0; - mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0); mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); } else { mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 6d2691c899f2..43e16dbeaeed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -38,6 +38,7 @@ import android.os.Looper; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.Choreographer; import android.view.View; import com.android.keyguard.KeyguardUpdateMonitor; @@ -374,7 +375,6 @@ public class ScrimControllerTest extends SysuiTestCase { onPreDraw(); // Force finish screen blanking. - endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK); mHandler.dispatchQueuedMessages(); // Force finish all animations. endAnimation(mScrimBehind, TAG_KEY_ANIM); @@ -401,6 +401,15 @@ public class ScrimControllerTest extends SysuiTestCase { protected WakeLock createWakeLock() { return mWakeLock; } + + /** + * Do not wait for a frame since we're in a test environment. + * @param callback What to execute. + */ + @Override + protected void doOnTheNextFrame(Choreographer.FrameCallback callback) { + callback.doFrame(0); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index a10bebfd4f2d..e1b97bdadadc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -29,12 +29,14 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest +@Ignore public class LocationControllerImplTest extends SysuiTestCase { private LocationControllerImpl mLocationController; @@ -79,4 +81,4 @@ public class LocationControllerImplTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java index f7bb0655b46e..922bde787a8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java @@ -16,6 +16,8 @@ package com.android.systemui.volume; +import static com.android.systemui.volume.OutputChooserDialog.INCLUDE_MEDIA_ROUTES; + import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -149,19 +151,13 @@ public class OutputChooserDialogTest extends SysuiTestCase { } @Test - public void testMediaScanIfNotInCall() { - mDialog.setIsInCall(false); - mDialog.onAttachedToWindow(); - - verify(mRouter, times(1)).addCallback(any(), any(), anyInt()); - } - - @Test public void testRegisterCallbacks() { mDialog.setIsInCall(false); mDialog.onAttachedToWindow(); - verify(mRouter, times(1)).addCallback(any(), any(), anyInt()); + if (INCLUDE_MEDIA_ROUTES) { + verify(mRouter, times(1)).addCallback(any(), any(), anyInt()); + } verify(mController, times(1)).addCallback(any()); verify(mVolumeController, times(1)).addCallback(any(), any()); } diff --git a/proto/Android.bp b/proto/Android.bp index 95f453c3e523..f3811bdd7d81 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -6,6 +6,8 @@ java_library_static { }, srcs: ["src/**/*.proto"], no_framework_libs: true, + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", target: { android: { jarjar_rules: "jarjar-rules.txt", diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 01714cf571b5..ae5e133fa02f 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5171,6 +5171,7 @@ message MetricsEvent { // An autofill service was bound using an unofficial(but still supported) permission. // Package: Package of the autofill service // OS: P + AUTOFILL_INVALID_PERMISSION = 1289; // OPEN: QS Alarm tile shown @@ -5185,6 +5186,33 @@ message MetricsEvent { // OS: P USB_DEVICE_DETAILS = 1291; + // OPEN: Settings > Accessibility > Vibration + // CATEGORY: SETTINGS + // OS: P + ACCESSIBILITY_VIBRATION = 1292; + + // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration + // CATEGORY: SETTINGS + // OS: P + ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293; + + // OPEN: Settings > Accessibility > Vibration > Touch vibration + // CATEGORY: SETTINGS + // OS: P + ACCESSIBILITY_VIBRATION_TOUCH = 1294; + + // OPEN: Volume panel > output chooser dialog + // OS: P + OUTPUT_CHOOSER = 1295; + + // Action: Volume panel > output chooser dialog > tap on device + // OS: P + ACTION_OUTPUT_CHOOSER_CONNECT = 1296; + + // Action: Volume panel > output chooser dialog > tap on X next to connected device + // OS: P + ACTION_OUTPUT_CHOOSER_DISCONNECT = 1297; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index b32be736533b..52d0e08e4e7f 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -24,8 +24,9 @@ #include <utils/misc.h> #include <inttypes.h> +#include <android-base/macros.h> #include <androidfw/Asset.h> -#include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include <androidfw/ResourceTypes.h> #include <android-base/macros.h> @@ -1664,18 +1665,22 @@ nFileA3DCreateFromAssetStream(JNIEnv *_env, jobject _this, jlong con, jlong nati static jlong nFileA3DCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path) { - AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr); + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr); if (mgr == nullptr) { return 0; } AutoJavaStringToUTF8 str(_env, _path); - Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - if (asset == nullptr) { - return 0; + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + if (asset == nullptr) { + return 0; + } } - jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset); + jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset.release()); return id; } @@ -1752,22 +1757,25 @@ static jlong nFontCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path, jfloat fontSize, jint dpi) { - AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr); + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr); if (mgr == nullptr) { return 0; } AutoJavaStringToUTF8 str(_env, _path); - Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - if (asset == nullptr) { - return 0; + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + if (asset == nullptr) { + return 0; + } } jlong id = (jlong)(uintptr_t)rsFontCreateFromMemory((RsContext)con, str.c_str(), str.length(), fontSize, dpi, asset->getBuffer(false), asset->getLength()); - delete asset; return id; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 297dcf16c17f..989a7b5619d2 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -861,8 +861,13 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("User: "); pw.println(mUserId); pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked()); - pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null - ? mInfo.getServiceInfo().getComponentName() : null); + pw.print(prefix); pw.print("Autofill Service Info: "); + if (mInfo == null) { + pw.println("N/A"); + } else { + pw.println(); + mInfo.dump(prefix2, pw); + } pw.print(prefix); pw.print("Component from settings: "); pw.println(getComponentNameFromSettings()); pw.print(prefix); pw.print("Default component: "); @@ -870,6 +875,7 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); + pw.print(prefix); pw.print("Compat pkgs: "); pw.println(getWhitelistedCompatModePackages()); pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); @@ -996,14 +1002,18 @@ final class AutofillManagerServiceImpl { } if (!Build.IS_ENG) { // TODO: Build a map and watch for settings changes (this is called on app start) - final String whiteListedPackages = Settings.Global.getString( - mContext.getContentResolver(), - Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES); + final String whiteListedPackages = getWhitelistedCompatModePackages(); return whiteListedPackages != null && whiteListedPackages.contains(packageName); } return true; } + private String getWhitelistedCompatModePackages() { + return Settings.Global.getString( + mContext.getContentResolver(), + Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES); + } + private void sendStateToClients(boolean resetClient) { final RemoteCallbackList<IAutoFillManagerClient> clients; final int userClientCount; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6108afa195ea..18f49ec5c454 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -947,7 +947,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int flags = lastResponse.getFlags(); if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) { - if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags); + if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags); return; } @@ -1431,7 +1431,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!"); + if (sDebug) { + Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " + + id + "!"); + } // Use handler so logContextCommitted() is logged first mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState)); @@ -1455,7 +1458,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Nothing changed... if (sDebug) { - Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities." + Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities." + "allRequiredAreNotNull=" + allRequiredAreNotEmpty + ", atLeastOneChanged=" + atLeastOneChanged); } @@ -1970,7 +1973,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { if (sVerbose) { Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds - + " (triggering on " + saveTriggerId + ")"); + + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish); } mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible, saveOnFinish, toArray(fillableIds), saveTriggerId); diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index f49cd6745e79..17c617c095d8 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -58,6 +58,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; import android.util.Pair; @@ -79,7 +80,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.Locale; import java.util.Random; @@ -224,10 +224,12 @@ class AlarmManagerService extends SystemService { interface Stats { int REBATCH_ALL_ALARMS = 0; + int REORDER_ALARMS_FOR_STANDBY = 1; } private final StatLogger mStatLogger = new StatLogger(new String[] { "REBATCH_ALL_ALARMS", + "REORDER_ALARMS_FOR_STANDBY", }); /** @@ -522,6 +524,10 @@ class AlarmManagerService extends SystemService { return newStart; } + boolean remove(Alarm alarm) { + return remove(a -> (a == alarm)); + } + boolean remove(Predicate<Alarm> predicate) { boolean didRemove = false; long newStart = 0; // recalculate endpoints as we go @@ -741,6 +747,23 @@ class AlarmManagerService extends SystemService { return (index == 0); } + private void insertAndBatchAlarmLocked(Alarm alarm) { + final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1 + : attemptCoalesceLocked(alarm.whenElapsed, alarm.maxWhenElapsed); + + if (whichBatch < 0) { + addBatchLocked(mAlarmBatches, new Batch(alarm)); + } else { + final Batch batch = mAlarmBatches.get(whichBatch); + if (batch.add(alarm)) { + // The start time of this batch advanced, so batch ordering may + // have just been broken. Move it to where it now belongs. + mAlarmBatches.remove(whichBatch); + addBatchLocked(mAlarmBatches, batch); + } + } + } + // Return the index of the matching batch, or -1 if none found. int attemptCoalesceLocked(long whenElapsed, long maxWhen) { final int N = mAlarmBatches.size(); @@ -794,7 +817,7 @@ class AlarmManagerService extends SystemService { } void rebatchAllAlarmsLocked(boolean doValidate) { - long start = mStatLogger.getTime(); + final long start = mStatLogger.getTime(); final int oldCount = getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms); final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches) @@ -837,6 +860,44 @@ class AlarmManagerService extends SystemService { mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start); } + /** + * Re-orders the alarm batches based on newly evaluated send times based on the current + * app-standby buckets + * @param targetPackages [Package, User] pairs for which alarms need to be re-evaluated, + * null indicates all + * @return True if there was any reordering done to the current list. + */ + boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) { + final long start = mStatLogger.getTime(); + final ArrayList<Alarm> rescheduledAlarms = new ArrayList<>(); + + for (int batchIndex = mAlarmBatches.size() - 1; batchIndex >= 0; batchIndex--) { + final Batch batch = mAlarmBatches.get(batchIndex); + for (int alarmIndex = batch.size() - 1; alarmIndex >= 0; alarmIndex--) { + final Alarm alarm = batch.get(alarmIndex); + final Pair<String, Integer> packageUser = + Pair.create(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid)); + if (targetPackages != null && !targetPackages.contains(packageUser)) { + continue; + } + if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) { + batch.remove(alarm); + rescheduledAlarms.add(alarm); + } + } + if (batch.size() == 0) { + mAlarmBatches.remove(batchIndex); + } + } + for (int i = 0; i < rescheduledAlarms.size(); i++) { + final Alarm a = rescheduledAlarms.get(i); + insertAndBatchAlarmLocked(a); + } + + mStatLogger.logDurationStat(Stats.REORDER_ALARMS_FOR_STANDBY, start); + return rescheduledAlarms.size() > 0; + } + void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) { a.when = a.origWhen; long whenElapsed = convertToElapsed(a.when, a.type); @@ -1442,18 +1503,32 @@ class AlarmManagerService extends SystemService { else return mConstants.APP_STANDBY_MIN_DELAYS[0]; } - private void adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) { + /** + * Adjusts the alarm delivery time based on the current app standby bucket. + * @param alarm The alarm to adjust + * @return true if the alarm delivery time was updated. + * TODO: Reduce the number of calls to getAppStandbyBucket by batching the calls per + * {package, user} pairs + */ + private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) { if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) { - return; + return false; + } + // TODO: short term fix for b/72816079, remove after a proper fix is in place + if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { + return false; } if (mAppStandbyParole) { if (alarm.whenElapsed > alarm.requestedWhenElapsed) { - // We did throttle this alarm earlier, restore original requirements + // We did defer this alarm earlier, restore original requirements alarm.whenElapsed = alarm.requestedWhenElapsed; alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed; } - return; + return true; } + final long oldWhenElapsed = alarm.whenElapsed; + final long oldMaxWhenElapsed = alarm.maxWhenElapsed; + final String sourcePackage = alarm.sourcePackage; final int sourceUserId = UserHandle.getUserId(alarm.creatorUid); final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket( @@ -1465,8 +1540,14 @@ class AlarmManagerService extends SystemService { final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket); if (alarm.requestedWhenElapsed < minElapsed) { alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; + } else { + // app is now eligible to run alarms at the originally requested window. + // Restore original requirements in case they were changed earlier. + alarm.whenElapsed = alarm.requestedWhenElapsed; + alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed; } } + return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed); } private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) { @@ -1521,21 +1602,7 @@ class AlarmManagerService extends SystemService { } } adjustDeliveryTimeBasedOnStandbyBucketLocked(a); - - int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0) - ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed); - if (whichBatch < 0) { - Batch batch = new Batch(a); - addBatchLocked(mAlarmBatches, batch); - } else { - Batch batch = mAlarmBatches.get(whichBatch); - if (batch.add(a)) { - // The start time of this batch advanced, so batch ordering may - // have just been broken. Move it to where it now belongs. - mAlarmBatches.remove(whichBatch); - addBatchLocked(mAlarmBatches, batch); - } - } + insertAndBatchAlarmLocked(a); if (a.alarmClock != null) { mNextAlarmClockMayChange = true; @@ -3138,7 +3205,7 @@ class AlarmManagerService extends SystemService { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); pw.print(prefix); pw.print("tag="); pw.println(statsTag); pw.print(prefix); pw.print("type="); pw.print(type); - pw.print(" requestedWhenELapsed="); TimeUtils.formatDuration( + pw.print(" requestedWhenElapsed="); TimeUtils.formatDuration( requestedWhenElapsed, nowELAPSED, pw); pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed, nowELAPSED, pw); @@ -3391,28 +3458,19 @@ class AlarmManagerService extends SystemService { } mPendingNonWakeupAlarms.clear(); } - boolean needRebatch = false; - final HashSet<String> triggerPackages = new HashSet<>(); - for (int i = triggerList.size() - 1; i >= 0; i--) { - triggerPackages.add(triggerList.get(i).sourcePackage); - } - outer: - for (int i = 0; i < mAlarmBatches.size(); i++) { - final Batch batch = mAlarmBatches.get(i); - for (int j = 0; j < batch.size(); j++) { - if (triggerPackages.contains(batch.get(j))) { - needRebatch = true; - break outer; - } + final ArraySet<Pair<String, Integer>> triggerPackages = + new ArraySet<>(); + for (int i = 0; i < triggerList.size(); i++) { + final Alarm a = triggerList.get(i); + if (!UserHandle.isCore(a.creatorUid)) { + triggerPackages.add(Pair.create( + a.sourcePackage, UserHandle.getUserId(a.creatorUid))); } } - if (needRebatch) { - rebatchAllAlarmsLocked(false); - } else { - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); - } deliverAlarmsLocked(triggerList, nowELAPSED); + reorderAlarmsBasedOnStandbyBuckets(triggerPackages); + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); } } @@ -3470,10 +3528,15 @@ class AlarmManagerService extends SystemService { public static final int REPORT_ALARMS_ACTIVE = 4; public static final int APP_STANDBY_BUCKET_CHANGED = 5; public static final int APP_STANDBY_PAROLE_CHANGED = 6; + public static final int REMOVE_FOR_STOPPED = 7; public AlarmHandler() { } + public void postRemoveForStopped(int uid) { + obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget(); + } + public void handleMessage(Message msg) { switch (msg.what) { case ALARM_EVENT: { @@ -3518,13 +3581,27 @@ class AlarmManagerService extends SystemService { case APP_STANDBY_PAROLE_CHANGED: synchronized (mLock) { mAppStandbyParole = (Boolean) msg.obj; - rebatchAllAlarmsLocked(false); + if (reorderAlarmsBasedOnStandbyBuckets(null)) { + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } } break; case APP_STANDBY_BUCKET_CHANGED: synchronized (mLock) { - rebatchAllAlarmsLocked(false); + final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>(); + filterPackages.add(Pair.create((String) msg.obj, msg.arg1)); + if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) { + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } + } + break; + + case REMOVE_FOR_STOPPED: + synchronized (mLock) { + removeForStoppedLocked(msg.arg1); } break; @@ -3717,10 +3794,8 @@ class AlarmManagerService extends SystemService { } @Override public void onUidGone(int uid, boolean disabled) { - synchronized (mLock) { - if (disabled) { - removeForStoppedLocked(uid); - } + if (disabled) { + mHandler.postRemoveForStopped(uid); } } @@ -3728,10 +3803,8 @@ class AlarmManagerService extends SystemService { } @Override public void onUidIdle(int uid, boolean disabled) { - synchronized (mLock) { - if (disabled) { - removeForStoppedLocked(uid); - } + if (disabled) { + mHandler.postRemoveForStopped(uid); } } @@ -3751,7 +3824,8 @@ class AlarmManagerService extends SystemService { bucket); } mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); - mHandler.sendEmptyMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName) + .sendToTarget(); } @Override diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java index 339101fa79bb..100680df637d 100644 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java @@ -614,48 +614,22 @@ public class ForceAppStandbyTracker { private final class UidObserver extends IUidObserver.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { - synchronized (mLock) { - if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - if (removeUidFromArray(mForegroundUids, uid, false)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } else { - if (addUidToArray(mForegroundUids, uid)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } - } + mHandler.onUidStateChanged(uid, procState); } @Override - public void onUidGone(int uid, boolean disabled) { - removeUid(uid, true); + public void onUidActive(int uid) { + mHandler.onUidActive(uid); } @Override - public void onUidActive(int uid) { - synchronized (mLock) { - if (addUidToArray(mActiveUids, uid)) { - mHandler.notifyUidActiveStateChanged(uid); - } - } + public void onUidGone(int uid, boolean disabled) { + mHandler.onUidGone(uid, disabled); } @Override public void onUidIdle(int uid, boolean disabled) { - // Just to avoid excessive memcpy, don't remove from the array in this case. - removeUid(uid, false); - } - - private void removeUid(int uid, boolean remove) { - synchronized (mLock) { - if (removeUidFromArray(mActiveUids, uid, remove)) { - mHandler.notifyUidActiveStateChanged(uid); - } - if (removeUidFromArray(mForegroundUids, uid, remove)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } + mHandler.onUidIdle(uid, disabled); } @Override @@ -740,6 +714,11 @@ public class ForceAppStandbyTracker { private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9; private static final int MSG_EXEMPT_CHANGED = 10; + private static final int MSG_ON_UID_STATE_CHANGED = 11; + private static final int MSG_ON_UID_ACTIVE = 12; + private static final int MSG_ON_UID_GONE = 13; + private static final int MSG_ON_UID_IDLE = 14; + public MyHandler(Looper looper) { super(looper); } @@ -790,6 +769,22 @@ public class ForceAppStandbyTracker { obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget(); } + public void onUidStateChanged(int uid, int procState) { + obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget(); + } + + public void onUidActive(int uid) { + obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget(); + } + + public void onUidGone(int uid, boolean disabled) { + obtainMessage(MSG_ON_UID_GONE, uid, disabled ? 1 : 0).sendToTarget(); + } + + public void onUidIdle(int uid, boolean disabled) { + obtainMessage(MSG_ON_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget(); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -883,6 +878,61 @@ public class ForceAppStandbyTracker { case MSG_USER_REMOVED: handleUserRemoved(msg.arg1); return; + + case MSG_ON_UID_STATE_CHANGED: + handleUidStateChanged(msg.arg1, msg.arg2); + return; + case MSG_ON_UID_ACTIVE: + handleUidActive(msg.arg1); + return; + case MSG_ON_UID_GONE: + handleUidGone(msg.arg1, msg.arg1 != 0); + return; + case MSG_ON_UID_IDLE: + handleUidIdle(msg.arg1, msg.arg1 != 0); + return; + } + } + + public void handleUidStateChanged(int uid, int procState) { + synchronized (mLock) { + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + if (removeUidFromArray(mForegroundUids, uid, false)) { + mHandler.notifyUidForegroundStateChanged(uid); + } + } else { + if (addUidToArray(mForegroundUids, uid)) { + mHandler.notifyUidForegroundStateChanged(uid); + } + } + } + } + + public void handleUidActive(int uid) { + synchronized (mLock) { + if (addUidToArray(mActiveUids, uid)) { + mHandler.notifyUidActiveStateChanged(uid); + } + } + } + + public void handleUidGone(int uid, boolean disabled) { + removeUid(uid, true); + } + + public void handleUidIdle(int uid, boolean disabled) { + // Just to avoid excessive memcpy, don't remove from the array in this case. + removeUid(uid, false); + } + + private void removeUid(int uid, boolean remove) { + synchronized (mLock) { + if (removeUidFromArray(mActiveUids, uid, remove)) { + mHandler.notifyUidActiveStateChanged(uid); + } + if (removeUidFromArray(mForegroundUids, uid, remove)) { + mHandler.notifyUidForegroundStateChanged(uid); + } } } } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 2f425859fec4..93f7f1d12922 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -2970,12 +2970,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub break; } - if (!didStart && attribute != null) { - if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() - || (controlFlags - & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) { - res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, - controlFlags, startInputReason); + if (!didStart) { + if (attribute != null) { + if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() + || (controlFlags + & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) { + res = startInputUncheckedLocked(cs, inputContext, missingMethods, + attribute, + controlFlags, startInputReason); + } else { + res = InputBindResult.NO_EDITOR; + } + } else { + res = InputBindResult.NULL_EDITOR_INFO; } } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 6743484b91c4..6747be340d46 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -35,7 +35,6 @@ import android.os.UserHandle; import android.telephony.CellInfo; import android.telephony.CellLocation; import android.telephony.DisconnectCause; -import android.telephony.LocationAccessPolicy; import android.telephony.PhoneStateListener; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; @@ -94,8 +93,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { IPhoneStateListener callback; IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; - int callerUid; - int callerPid; + int callerUserId; int events; @@ -119,7 +117,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { + " callback=" + callback + " onSubscriptionsChangedListenererCallback=" + onSubscriptionsChangedListenerCallback - + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId + + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId + " events=" + Integer.toHexString(events) + " canReadPhoneState=" + canReadPhoneState + "}"; } @@ -358,8 +356,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { public void addOnSubscriptionsChangedListener(String callingPackage, IOnSubscriptionsChangedListener callback) { int callerUserId = UserHandle.getCallingUserId(); - mContext.getSystemService(AppOpsManager.class) - .checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId + " callback=" + callback @@ -403,8 +399,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.onSubscriptionsChangedListenerCallback = callback; r.callingPackage = callingPackage; - r.callerUid = Binder.getCallingUid(); - r.callerPid = Binder.getCallingPid(); + r.callerUserId = callerUserId; r.events = 0; r.canReadPhoneState = true; // permission has been enforced above if (DBG) { @@ -475,8 +470,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void listen(String callingPackage, IPhoneStateListener callback, int events, boolean notifyNow, int subId) { int callerUserId = UserHandle.getCallingUserId(); - mContext.getSystemService(AppOpsManager.class) - .checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events) + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId=" @@ -521,8 +514,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback = callback; r.callingPackage = callingPackage; - r.callerUid = Binder.getCallingUid(); - r.callerPid = Binder.getCallingPid(); + r.callerUserId = callerUserId; boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0; r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage); @@ -580,10 +572,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { - r.callback.onCellLocationChanged( - new Bundle(mCellLocation[phoneId])); - } + r.callback.onCellLocationChanged( + new Bundle(mCellLocation[phoneId])); } catch (RemoteException ex) { remove(r.binder); } @@ -629,9 +619,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " + mCellInfo.get(phoneId)); - if (checkLocationAccess(r)) { - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); - } + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } catch (RemoteException ex) { remove(r.binder); } @@ -991,8 +979,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellInfo.set(phoneId, cellInfo); for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) && - idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -1275,8 +1262,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellLocation[phoneId] = cellLocation; for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) && - idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { log("notifyCellLocation: cellLocation=" + cellLocation @@ -1720,11 +1706,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { boolean valid = false; try { foregroundUser = ActivityManager.getCurrentUser(); - valid = UserHandle.getUserId(r.callerUid) == foregroundUser - && r.matchPhoneStateListenerEvent(events); + valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events); if (DBG | DBG_LOC) { log("validateEventsAndUserLocked: valid=" + valid - + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser + + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser + " r.events=" + r.events + " events=" + events); } } finally { @@ -1756,16 +1741,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - private boolean checkLocationAccess(Record r) { - long token = Binder.clearCallingIdentity(); - try { - return LocationAccessPolicy.canAccessCellLocation(mContext, - r.callingPackage, r.callerUid, r.callerPid); - } finally { - Binder.restoreCallingIdentity(token); - } - } - private void checkPossibleMissNotify(Record r, int phoneId) { int events = r.events; @@ -1813,9 +1788,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " + mCellInfo.get(phoneId)); } - if (checkLocationAccess(r)) { - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); - } + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1863,9 +1836,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { - r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); - } + r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); } catch (RemoteException ex) { mRemoveList.add(r.binder); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 99f36d0a8b1b..2d443a325182 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1854,7 +1854,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int REPORT_MEM_USAGE_MSG = 33; static final int IMMERSIVE_MODE_LOCK_MSG = 37; static final int PERSIST_URI_GRANTS_MSG = 38; - static final int REQUEST_ALL_PSS_MSG = 39; static final int UPDATE_TIME_PREFERENCE_MSG = 41; static final int ENTER_ANIMATION_COMPLETE_MSG = 44; static final int FINISH_BOOTING_MSG = 45; @@ -2320,12 +2319,6 @@ public class ActivityManagerService extends IActivityManager.Stub writeGrantedUriPermissions(); break; } - case REQUEST_ALL_PSS_MSG: { - synchronized (ActivityManagerService.this) { - requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); - } - break; - } case UPDATE_TIME_PREFERENCE_MSG: { // The user's time format preference might have changed. // For convenience we re-use the Intent extra values. @@ -2615,11 +2608,17 @@ public class ActivityManagerService extends IActivityManager.Stub procState = proc.pssProcState; statType = proc.pssStatType; lastPssTime = proc.lastPssTime; + long now = SystemClock.uptimeMillis(); if (proc.thread != null && procState == proc.setProcState && (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) - < SystemClock.uptimeMillis()) { + < now) { pid = proc.pid; } else { + ProcessList.abortNextPssTime(proc.procStateMemTracker); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid + + ": still need " + + (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE-now) + + "ms until safe"); proc = null; pid = 0; } @@ -11316,8 +11315,11 @@ public class ActivityManagerService extends IActivityManager.Stub long ident = Binder.clearCallingIdentity(); try { synchronized (this) { - startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId), - true /* isSystemCaller */); + final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + + // When starting lock task mode the stack must be in front and focused + task.getStack().moveToFront("startSystemLockTaskMode"); + startLockTaskModeLocked(task, true /* isSystemCaller */); } } finally { Binder.restoreCallingIdentity(ident); @@ -14477,9 +14479,6 @@ public class ActivityManagerService extends IActivityManager.Stub mTestPssMode, isSleepingLocked(), now); } } - - mHandler.removeMessages(REQUEST_ALL_PSS_MSG); - mHandler.sendEmptyMessageDelayed(REQUEST_ALL_PSS_MSG, 2*60*1000); } } @@ -19526,6 +19525,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessesToGc.remove(app); mPendingPssProcesses.remove(app); + ProcessList.abortNextPssTime(app.procStateMemTracker); // Dismiss any open dialogs. if (app.crashDialog != null && !app.forceCrashReport) { @@ -23290,9 +23290,9 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Schedule PSS collection of a process. */ - void requestPssLocked(ProcessRecord proc, int procState) { + boolean requestPssLocked(ProcessRecord proc, int procState) { if (mPendingPssProcesses.contains(proc)) { - return; + return false; } if (mPendingPssProcesses.size() == 0) { mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); @@ -23301,6 +23301,7 @@ public class ActivityManagerService extends IActivityManager.Stub proc.pssProcState = procState; proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE; mPendingPssProcesses.add(proc); + return true; } /** @@ -23317,6 +23318,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); mLastFullPssTime = now; mFullPssPending = true; + for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) { + ProcessList.abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker);; + } mPendingPssProcesses.ensureCapacity(mLruProcesses.size()); mPendingPssProcesses.clear(); for (int i = mLruProcesses.size() - 1; i >= 0; i--) { @@ -23325,7 +23329,9 @@ public class ActivityManagerService extends IActivityManager.Stub || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) { continue; } - if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { + if (memLowered || (always && now > + app.lastStateTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) + || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { app.pssProcState = app.setProcState; app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM; @@ -23334,7 +23340,9 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingPssProcesses.add(app); } } - mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); + if (!mBgHandler.hasMessages(COLLECT_PSS_BG_MSG)) { + mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); + } } public void setTestPssMode(boolean enabled) { @@ -23692,7 +23700,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Experimental code to more aggressively collect pss while // running test... the problem is that this tends to collect // the data right when a process is transitioning between process - // states, which well tend to give noisy data. + // states, which will tend to give noisy data. long start = SystemClock.uptimeMillis(); long startTime = SystemClock.currentThreadTimeMillis(); long pss = Debug.getPss(app.pid, mTmpLong, null); @@ -23715,9 +23723,10 @@ public class ActivityManagerService extends IActivityManager.Stub if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL) && now > (app.lastStateTime+ProcessList.minTimeFromStateChange( mTestPssMode)))) { - requestPssLocked(app, app.setProcState); - app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, - app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); + if (requestPssLocked(app, app.setProcState)) { + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, + app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); + } } else if (false && DEBUG_PSS) Slog.d(TAG_PSS, "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now)); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 77f6d44e6670..a0f31cd213b7 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1266,7 +1266,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); int modifiedFlags = flags | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; - if (intent.isBrowsableWebIntent() + if (intent.isWebIntent() || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { modifiedFlags |= PackageManager.MATCH_INSTANT; } diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java index 9c2ee873fa6d..9033b55f0b4f 100644 --- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java @@ -41,7 +41,7 @@ class GlobalSettingsToPropertiesMapper { // {Settings.Global.SETTING_NAME, "system_property_name"}, {Settings.Global.SYS_VDSO, "sys.vdso"}, {Settings.Global.FPS_DEVISOR, ThreadedRenderer.DEBUG_FPS_DIVISOR}, - {Settings.Global.UID_CPUPOWER, "uid.cpupower"}, + {Settings.Global.DISPLAY_PANEL_LPM, "sys.display_panel_lpm"}, }; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 08ee23723df4..bf7aef9ecc65 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -501,19 +501,19 @@ public final class ProcessList { private static final int PSS_FIRST_CACHED_INTERVAL = 20*1000; // The amount of time until PSS when an important process stays in the same state. - private static final int PSS_SAME_PERSISTENT_INTERVAL = 20*60*1000; + private static final int PSS_SAME_PERSISTENT_INTERVAL = 10*60*1000; // The amount of time until PSS when the top process stays in the same state. - private static final int PSS_SAME_TOP_INTERVAL = 5*60*1000; + private static final int PSS_SAME_TOP_INTERVAL = 1*60*1000; // The amount of time until PSS when an important process stays in the same state. - private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000; + private static final int PSS_SAME_IMPORTANT_INTERVAL = 10*60*1000; // The amount of time until PSS when a service process stays in the same state. - private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000; + private static final int PSS_SAME_SERVICE_INTERVAL = 5*60*1000; // The amount of time until PSS when a cached process stays in the same state. - private static final int PSS_SAME_CACHED_INTERVAL = 20*60*1000; + private static final int PSS_SAME_CACHED_INTERVAL = 10*60*1000; // The amount of time until PSS when a persistent process first appears. private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000; @@ -622,16 +622,17 @@ public final class ProcessList { public static final class ProcStateMemTracker { final int[] mHighestMem = new int[PROC_MEM_NUM]; + final float[] mScalingFactor = new float[PROC_MEM_NUM]; int mTotalHighestMem = PROC_MEM_CACHED; - float mCurFactor = 1.0f; int mPendingMemState; int mPendingHighestMemState; - boolean mPendingSame; + float mPendingScalingFactor; public ProcStateMemTracker() { for (int i = PROC_MEM_PERSISTENT; i < PROC_MEM_NUM; i++) { mHighestMem[i] = PROC_MEM_NUM; + mScalingFactor[i] = 1.0f; } mPendingMemState = -1; } @@ -639,16 +640,22 @@ public final class ProcessList { public void dumpLine(PrintWriter pw) { pw.print("best="); pw.print(mTotalHighestMem); - pw.print(" "); - pw.print(mCurFactor); - pw.print("x ("); + pw.print(" ("); + boolean needSep = false; for (int i = 0; i < PROC_MEM_NUM; i++) { - if (i != 0) { - pw.print(", "); + if (mHighestMem[i] < PROC_MEM_NUM) { + if (needSep) { + pw.print(", "); + needSep = false; + } + pw.print(i); + pw.print("="); + pw.print(mHighestMem[i]); + pw.print(" "); + pw.print(mScalingFactor[i]); + pw.print("x"); + needSep = true; } - pw.print(i); - pw.print("="); - pw.print(mHighestMem[i]); } pw.print(")"); if (mPendingMemState >= 0) { @@ -656,8 +663,9 @@ public final class ProcessList { pw.print(mPendingMemState); pw.print(" highest="); pw.print(mPendingHighestMemState); - pw.print(" same="); - pw.print(mPendingSame); + pw.print(" "); + pw.print(mPendingScalingFactor); + pw.print("x"); } pw.println(); } @@ -674,12 +682,8 @@ public final class ProcessList { public static void commitNextPssTime(ProcStateMemTracker tracker) { if (tracker.mPendingMemState >= 0) { tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState; + tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor; tracker.mTotalHighestMem = tracker.mPendingHighestMemState; - if (tracker.mPendingSame) { - tracker.mCurFactor *= 1.5f; - } else { - tracker.mCurFactor = 1; - } tracker.mPendingMemState = -1; } } @@ -691,6 +695,7 @@ public final class ProcessList { public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, boolean sleeping, long now) { boolean first; + float scalingFactor; final int memState = sProcStateToProcMem[procState]; if (tracker != null) { final int highestMemState = memState < tracker.mTotalHighestMem @@ -698,9 +703,15 @@ public final class ProcessList { first = highestMemState < tracker.mHighestMem[memState]; tracker.mPendingMemState = memState; tracker.mPendingHighestMemState = highestMemState; - tracker.mPendingSame = !first; + if (first) { + tracker.mPendingScalingFactor = scalingFactor = 1.0f; + } else { + scalingFactor = tracker.mScalingFactor[memState]; + tracker.mPendingScalingFactor = scalingFactor * 1.5f; + } } else { first = true; + scalingFactor = 1.0f; } final long[] table = test ? (first @@ -709,8 +720,7 @@ public final class ProcessList { : (first ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes) : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes)); - long delay = (long)(table[memState] * (tracker != null && !first - ? tracker.mCurFactor : 1.0f)); + long delay = (long)(table[memState] * scalingFactor); if (delay > PSS_MAX_INTERVAL) { delay = PSS_MAX_INTERVAL; } diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index 8bf320e0c387..edf565abd7df 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -237,6 +237,7 @@ public final class ProcessStatsService extends IProcessStats.Stub { if (commit) { mProcessStats.resetSafely(); updateFile(); + mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); } mLastWriteTime = SystemClock.uptimeMillis(); totalTime = SystemClock.uptimeMillis() - now; @@ -784,12 +785,14 @@ public final class ProcessStatsService extends IProcessStats.Stub { } else if ("--reset".equals(arg)) { synchronized (mAm) { mProcessStats.resetSafely(); + mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); pw.println("Process stats reset."); quit = true; } } else if ("--clear".equals(arg)) { synchronized (mAm) { mProcessStats.resetSafely(); + mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); ArrayList<String> files = getCommittedFiles(0, true, true); if (files != null) { for (int fi=0; fi<files.size(); fi++) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 7b0c714b0b4e..d54e2640ceed 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -743,7 +743,7 @@ class UserController implements Handler.Callback { mInjector.stackSupervisorRemoveUser(userId); // Remove the user if it is ephemeral. if (getUserInfo(userId).isEphemeral()) { - mInjector.getUserManager().removeUser(userId); + mInjector.getUserManager().removeUserEvenWhenDisallowed(userId); } // Evict the user's credential encryption key. try { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1825db8647bf..8635cff7ecf9 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -23,6 +23,7 @@ import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.os.Process.FIRST_APPLICATION_UID; import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -240,6 +241,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25; private static final int MSG_INDICATE_SYSTEM_READY = 26; private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27; + private static final int MSG_NOTIFY_VOL_EVENT = 28; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -1334,11 +1336,9 @@ public class AudioService extends IAudioService.Stub extVolCtlr = mExtVolumeController; } if (extVolCtlr != null) { - try { - mExtVolumeController.notifyVolumeAdjust(direction); - } catch(RemoteException e) { - // nothing we can do about this. Do not log error, too much potential for spam - } + sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE, + direction, 0 /*ignored*/, + extVolCtlr, 0 /*delay*/); } else { adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, caller, Binder.getCallingUid()); @@ -5054,6 +5054,15 @@ public class AudioService extends IAudioService.Stub state); } + private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc, + @AudioManager.VolumeAdjustment int direction) { + try { + apc.notifyVolumeAdjust(direction); + } catch(Exception e) { + // nothing we can do about this. Do not log error, too much potential for spam + } + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -5216,6 +5225,10 @@ public class AudioService extends IAudioService.Stub case MSG_DYN_POLICY_MIX_STATE_UPDATE: onDynPolicyMixStateUpdate((String) msg.obj, msg.arg1); break; + + case MSG_NOTIFY_VOL_EVENT: + onNotifyVolumeEvent((IAudioPolicyCallback) msg.obj, msg.arg1); + break; } } } @@ -7296,6 +7309,12 @@ public class AudioService extends IAudioService.Stub //====================== /** */ public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (pcb == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy callback"); + } synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch"); @@ -7304,6 +7323,23 @@ public class AudioService extends IAudioService.Stub } } + public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult, + IAudioPolicyCallback pcb) { + if (afi == null) { + throw new IllegalArgumentException("Illegal null AudioFocusInfo"); + } + if (pcb == null) { + throw new IllegalArgumentException("Illegal null AudioPolicy callback"); + } + synchronized (mAudioPolicies) { + if (!mAudioPolicies.containsKey(pcb.asBinder())) { + throw new IllegalStateException("Unregistered AudioPolicy for external focus"); + } + mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult); + } + } + + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index f2ef02fb2579..99f08405a375 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -241,15 +241,15 @@ public class FocusRequester { void release() { + final IBinder srcRef = mSourceRef; + final AudioFocusDeathHandler deathHdlr = mDeathHandler; try { - if (mSourceRef != null && mDeathHandler != null) { - mSourceRef.unlinkToDeath(mDeathHandler, 0); - mDeathHandler = null; - mFocusDispatcher = null; + if (srcRef != null && deathHdlr != null) { + srcRef.unlinkToDeath(deathHdlr, 0); } - } catch (java.util.NoSuchElementException e) { - Log.e(TAG, "FocusRequester.release() hit ", e); - } + } catch (java.util.NoSuchElementException e) { } + mDeathHandler = null; + mFocusDispatcher = null; } @Override @@ -424,7 +424,7 @@ public class FocusRequester { int dispatchFocusChange(int focusChange) { if (mFocusDispatcher == null) { - if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); } + if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } if (focusChange == AudioManager.AUDIOFOCUS_NONE) { @@ -445,12 +445,29 @@ public class FocusRequester { try { mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId); } catch (android.os.RemoteException e) { - Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e); + Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } + void dispatchFocusResultFromExtPolicy(int requestResult) { + if (mFocusDispatcher == null) { + if (MediaFocusControl.DEBUG) { + Log.e(TAG, "dispatchFocusResultFromExtPolicy: no focus dispatcher"); + } + } + if (DEBUG) { + Log.v(TAG, "dispatching result" + requestResult + " to " + mClientId); + } + try { + mFocusDispatcher.dispatchFocusResultFromExtPolicy(requestResult, mClientId); + } catch (android.os.RemoteException e) { + Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener" + + mClientId, e); + } + } + AudioFocusInfo toAudioFocusInfo() { return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName, mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget); diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 9ddc52a1826e..d023bd7827ff 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -83,6 +83,10 @@ public class MediaFocusControl implements PlayerFocusEnforcer { private boolean mRingOrCallActive = false; + private final Object mExtFocusChangeLock = new Object(); + @GuardedBy("mExtFocusChangeLock") + private long mExtFocusChangeCounter; + protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) { mContext = cntxt; mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); @@ -521,7 +525,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @param requestResult * @return true if the external audio focus policy (if any) is handling the focus request */ - boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, int requestResult, + boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, IAudioFocusDispatcher fd, IBinder cb) { if (mFocusPolicy == null) { return false; @@ -530,6 +534,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId() + " dispatcher=" + fd); } + synchronized (mExtFocusChangeLock) { + afi.setGen(mExtFocusChangeCounter++); + } final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); if (existingFr != null) { if (!existingFr.hasSameDispatcher(fd)) { @@ -538,8 +545,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusOwnersForFocusPolicy.put(afi.getClientId(), new FocusRequester(afi, fd, cb, hdlr, this)); } - } else if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED - || requestResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { + } else { // new focus (future) focus owner to keep track of final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb); mFocusOwnersForFocusPolicy.put(afi.getClientId(), @@ -547,12 +553,25 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } try { //oneway - mFocusPolicy.notifyAudioFocusRequest(afi, requestResult); + mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + return true; } catch (RemoteException e) { Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback " + mFocusPolicy.asBinder(), e); } - return true; + return false; + } + + void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) { + synchronized (mExtFocusChangeLock) { + if (afi.getGen() > mExtFocusChangeCounter) { + return; + } + } + final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + if (fr != null) { + fr.dispatchFocusResultFromExtPolicy(requestResult); + } } /** @@ -590,7 +609,12 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + final FocusRequester fr; + if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId()); + } else { + fr = mFocusOwnersForFocusPolicy.get(afi.getClientId()); + } if (fr == null) { if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); } return AudioManager.AUDIOFOCUS_REQUEST_FAILED; @@ -710,9 +734,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { boolean focusGrantDelayed = false; if (!canReassignAudioFocus()) { if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { - final int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED; - notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, result, fd, cb); - return result; + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } else { // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be // granted right now, so the requester will be inserted in the focus stack @@ -721,12 +743,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } } - // external focus policy: delay request for focus gain? - final int resultWithExtPolicy = AudioManager.AUDIOFOCUS_REQUEST_DELAYED; + // external focus policy? if (notifyExtFocusPolicyFocusRequest_syncAf( - afiForExtPolicy, resultWithExtPolicy, fd, cb)) { + afiForExtPolicy, fd, cb)) { // stop handling focus request here as it is handled by external audio focus policy - return resultWithExtPolicy; + return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY; } // handle the potential premature death of the new holder of the focus diff --git a/services/core/java/com/android/server/car/CarServiceHelperService.java b/services/core/java/com/android/server/car/CarServiceHelperService.java deleted file mode 100644 index 9392a6a8fcd9..000000000000 --- a/services/core/java/com/android/server/car/CarServiceHelperService.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2017 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.car; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcel; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Slog; - -import com.android.internal.car.ICarServiceHelper; -import com.android.server.SystemService; - -/** - * System service side companion service for CarService. - * Starts car service and provide necessary API for CarService. Only for car product. - */ -public class CarServiceHelperService extends SystemService { - private static final String TAG = "CarServiceHelper"; - private static final String CAR_SERVICE_INTERFACE = "android.car.ICar"; - private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl(); - private IBinder mCarService; - private final ServiceConnection mCarServiceConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - Slog.i(TAG, "**CarService connected**"); - mCarService = iBinder; - // Cannot depend on ICar which is defined in CarService, so handle binder call directly - // instead. - // void setCarServiceHelper(in IBinder helper) - Parcel data = Parcel.obtain(); - data.writeInterfaceToken(CAR_SERVICE_INTERFACE); - data.writeStrongBinder(mHelper.asBinder()); - try { - mCarService.transact(IBinder.FIRST_CALL_TRANSACTION, // setCarServiceHelper - data, null, Binder.FLAG_ONEWAY); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException from car service", e); - handleCarServiceCrash(); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - handleCarServiceCrash(); - } - }; - - public CarServiceHelperService(Context context) { - super(context); - } - - @Override - public void onStart() { - Intent intent = new Intent(); - intent.setPackage("com.android.car"); - intent.setAction(CAR_SERVICE_INTERFACE); - if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE, - UserHandle.SYSTEM)) { - Slog.wtf(TAG, "cannot start car service"); - } - } - - private void handleCarServiceCrash() { - //TODO define recovery bahavior - } - - private class ICarServiceHelperImpl extends ICarServiceHelper.Stub { - //TODO - } -} diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java index 51499f735bcf..9980930d87c8 100644 --- a/services/core/java/com/android/server/content/SyncJobService.java +++ b/services/core/java/com/android/server/content/SyncJobService.java @@ -137,11 +137,13 @@ public class SyncJobService extends JobService { + " params=" + jobParametersToString(params)); } } else if (runtime < 10 * 1000) { - // Job stopped too soon. WTF. - wtf("Job " + jobId + " stopped in " + runtime + " ms: " - + " startUptime=" + startUptime - + " nowUptime=" + nowUptime - + " params=" + jobParametersToString(params)); + // This happens too in a normal case too, and it's rather too often. + // Disable it for now. +// // Job stopped too soon. WTF. +// wtf("Job " + jobId + " stopped in " + runtime + " ms: " +// + " startUptime=" + startUptime +// + " nowUptime=" + nowUptime +// + " params=" + jobParametersToString(params)); } mStartedSyncs.delete(jobId); diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 373d87d971b8..13873e476894 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -328,10 +328,12 @@ public final class ConnectivityController extends StateController implements @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { pw.print("Connectivity: connected="); - pw.print(mConnected); + pw.println(mConnected); + pw.print("Tracking "); pw.print(mTrackedJobs.size()); - pw.println(":"); + pw.println(" jobs"); + for (int i = 0; i < mTrackedJobs.size(); i++) { final JobStatus js = mTrackedJobs.valueAt(i); if (js.shouldDump(filterUid)) { @@ -339,7 +341,9 @@ public final class ConnectivityController extends StateController implements js.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, js.getSourceUid()); - pw.print(": "); pw.print(js.getJob().getRequiredNetwork()); + pw.print(": "); + pw.print(js.getJob().getRequiredNetwork()); + pw.println(); } } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index 3a78f950675e..e5ff5b83704f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -194,6 +194,9 @@ public class PlatformKeyManager { UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { init(userId); try { + // Try to see if the decryption key is still accessible before using the encryption key. + // The auth-bound decryption will be unrecoverable if the screen lock is disabled. + getDecryptKeyInternal(userId); return getEncryptKeyInternal(userId); } catch (UnrecoverableKeyException e) { Log.i(TAG, String.format(Locale.US, @@ -219,7 +222,7 @@ public class PlatformKeyManager { UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { int generationId = getGenerationId(userId); String alias = getEncryptAlias(userId, generationId); - if (!mKeyStore.containsAlias(alias)) { + if (!isKeyLoaded(userId, generationId)) { throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); } AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( @@ -268,7 +271,7 @@ public class PlatformKeyManager { UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { int generationId = getGenerationId(userId); String alias = getDecryptAlias(userId, generationId); - if (!mKeyStore.containsAlias(alias)) { + if (!isKeyLoaded(userId, generationId)) { throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); } AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( @@ -300,12 +303,12 @@ public class PlatformKeyManager { return; } if (generationId == -1) { - Log.i(TAG, "Generating initial platform ID."); + Log.i(TAG, "Generating initial platform key generation ID."); generationId = 1; } else { Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no " + "entry was present in AndroidKeyStore. Generating fresh key.", generationId)); - // Had to generate a fresh key, bump the generation id + // Have to generate a fresh key, so bump the generation id generationId++; } @@ -374,7 +377,7 @@ public class PlatformKeyManager { String decryptAlias = getDecryptAlias(userId, generationId); SecretKey secretKey = generateAesKey(); - // Store Since decryption key first since it is more likely to fail. + // Store decryption key first since it is more likely to fail. mKeyStore.setEntry( decryptAlias, new KeyStore.SecretKeyEntry(secretKey), @@ -386,7 +389,6 @@ public class PlatformKeyManager { .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setBoundToSpecificSecureUserId(userId) .build()); - mKeyStore.setEntry( encryptAlias, new KeyStore.SecretKeyEntry(secretKey), @@ -397,11 +399,7 @@ public class PlatformKeyManager { setGenerationId(userId, generationId); - try { - secretKey.destroy(); - } catch (DestroyFailedException e) { - Log.w(TAG, "Failed to destroy in-memory platform key.", e); - } + // TODO: Use a reliable way to destroy the temporary secretKey in memory. } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index fda6cdf33e0c..33e767fe1a8f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -519,11 +519,11 @@ public class RecoverableKeyStoreManager { byte[] locallyEncryptedKey; try { // TODO: Remove the extraneous logging here - Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()", + Log.d(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()", sessionEntry.getKeyClaimant())); - Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()", + Log.d(TAG, constructLoggingMessage("sessionEntry.getVaultParams()", sessionEntry.getVaultParams())); - Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse)); + Log.d(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse)); locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( sessionEntry.getKeyClaimant(), sessionEntry.getVaultParams(), @@ -543,9 +543,9 @@ public class RecoverableKeyStoreManager { try { // TODO: Remove the extraneous logging here - Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()", + Log.d(TAG, constructLoggingMessage("sessionEntry.getLskfHash()", sessionEntry.getLskfHash())); - Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey)); + Log.d(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey)); return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); } catch (InvalidKeyException e) { Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e); @@ -585,8 +585,8 @@ public class RecoverableKeyStoreManager { try { // TODO: Remove the extraneous logging here - Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); - Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); + Log.d(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); + Log.d(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial); keyMaterialByAlias.put(alias, keyMaterial); @@ -600,13 +600,16 @@ public class RecoverableKeyStoreManager { throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to recover key with alias '" + alias + "': " + e.getMessage()); } catch (AEADBadTagException e) { - // TODO: Remove the extraneous logging here Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: " + alias, e); - throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, - "Failed to recover key with alias '" + alias + "': " + e.getMessage()); + // Ignore the exception to continue to recover the other application keys. } } + if (keyMaterialByAlias.isEmpty()) { + Log.e(TAG, "Failed to recover any of the application keys."); + throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, + "Failed to recover any of the application keys."); + } return keyMaterialByAlias; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java index 985f5b61dc31..fea6733dd1f5 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java @@ -18,6 +18,7 @@ package com.android.server.locksettings.recoverablekeystore.certificate; import static javax.xml.xpath.XPathConstants.NODESET; +import android.annotation.IntDef; import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -25,6 +26,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -72,13 +75,14 @@ final class CertUtils { private static final String CERT_STORE_ALG = "Collection"; private static final String SIGNATURE_ALG = "SHA256withRSA"; - private CertUtils() {} + @Retention(RetentionPolicy.SOURCE) + @IntDef({MUST_EXIST_UNENFORCED, MUST_EXIST_EXACTLY_ONE, MUST_EXIST_AT_LEAST_ONE}) + @interface MustExist {} + static final int MUST_EXIST_UNENFORCED = 0; + static final int MUST_EXIST_EXACTLY_ONE = 1; + static final int MUST_EXIST_AT_LEAST_ONE = 2; - enum MustExist { - FALSE, - EXACTLY_ONE, - AT_LEAST_ONE, - } + private CertUtils() {} /** * Decodes a byte array containing an encoded X509 certificate. @@ -159,7 +163,7 @@ final class CertUtils { * @return a list of strings that are the text contents of the child nodes * @throws CertParsingException if any parsing error occurs */ - static List<String> getXmlNodeContents(MustExist mustExist, Element rootNode, + static List<String> getXmlNodeContents(@MustExist int mustExist, Element rootNode, String... nodeTags) throws CertParsingException { String expression = String.join("/", nodeTags); @@ -173,10 +177,10 @@ final class CertUtils { } switch (mustExist) { - case FALSE: + case MUST_EXIST_UNENFORCED: break; - case EXACTLY_ONE: + case MUST_EXIST_EXACTLY_ONE: if (nodeList.getLength() != 1) { throw new CertParsingException( "The XML file must contain exactly one node with the path " @@ -184,7 +188,7 @@ final class CertUtils { } break; - case AT_LEAST_ONE: + case MUST_EXIST_AT_LEAST_ONE: if (nodeList.getLength() == 0) { throw new CertParsingException( "The XML file must contain at least one node with the path " @@ -194,7 +198,7 @@ final class CertUtils { default: throw new UnsupportedOperationException( - "This enum value of MustExist is not supported: " + mustExist); + "This value of MustExist is not supported: " + mustExist); } List<String> result = new ArrayList<>(); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java index 2c04a861abb6..c62a31e24fb2 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java @@ -129,7 +129,7 @@ public final class CertXml { private static long parseSerial(Element rootNode) throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.EXACTLY_ONE, + CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, METADATA_NODE_TAG, METADATA_SERIAL_NODE_TAG); @@ -139,7 +139,7 @@ public final class CertXml { private static long parseRefreshInterval(Element rootNode) throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.EXACTLY_ONE, + CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, METADATA_NODE_TAG, METADATA_REFRESH_INTERVAL_NODE_TAG); @@ -150,7 +150,7 @@ public final class CertXml { throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.FALSE, + CertUtils.MUST_EXIST_UNENFORCED, rootNode, INTERMEDIATE_CERT_LIST_TAG, INTERMEDIATE_CERT_ITEM_TAG); @@ -165,7 +165,7 @@ public final class CertXml { throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.AT_LEAST_ONE, + CertUtils.MUST_EXIST_AT_LEAST_ONE, rootNode, ENDPOINT_CERT_LIST_TAG, ENDPOINT_CERT_ITEM_TAG); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java index 878fc6edf77c..e75be8574254 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java @@ -94,7 +94,7 @@ public final class SigXml { throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.FALSE, + CertUtils.MUST_EXIST_UNENFORCED, rootNode, INTERMEDIATE_CERT_LIST_TAG, INTERMEDIATE_CERT_ITEM_TAG); @@ -108,14 +108,14 @@ public final class SigXml { private static X509Certificate parseSignerCert(Element rootNode) throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG); + CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG); return CertUtils.decodeCert(CertUtils.decodeBase64(contents.get(0))); } private static byte[] parseFileSignature(Element rootNode) throws CertParsingException { List<String> contents = CertUtils.getXmlNodeContents( - CertUtils.MustExist.EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG); + CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG); return CertUtils.decodeBase64(contents.get(0)); } } diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java index c4de4ac1b7dc..81fe641a6409 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -128,7 +128,7 @@ class WatchlistLoggingHandler extends Handler { Slog.e(TAG, "Couldn't find package: " + packageNames); return false; } - ai = mPm.getApplicationInfo(packageNames[0],0); + ai = mPm.getApplicationInfo(packageNames[0], 0); } catch (NameNotFoundException e) { // Should not happen. return false; @@ -136,7 +136,7 @@ class WatchlistLoggingHandler extends Handler { return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; } - /** + /** * Report network watchlist records if we collected enough data. */ public void reportWatchlistIfNecessary() { @@ -180,6 +180,10 @@ class WatchlistLoggingHandler extends Handler { return true; } final byte[] digest = getDigestFromUid(uid); + if (digest == null) { + Slog.e(TAG, "Cannot get digest from uid: " + uid); + return false; + } final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp); tryAggregateRecords(); return result; @@ -242,6 +246,11 @@ class WatchlistLoggingHandler extends Handler { final int size = apps.size(); for (int i = 0; i < size; i++) { byte[] digest = getDigestFromUid(apps.get(i).uid); + if (digest == null) { + Slog.e(TAG, "Cannot get digest from uid: " + apps.get(i).uid + + ",pkg: " + apps.get(i).packageName); + continue; + } result.add(HexDump.toHexString(digest)); } // Step 2: Add all digests from records diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index fd435f952c10..b7842d5365b5 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -376,9 +376,7 @@ abstract public class ManagedServices { protected void upgradeXml(final int xmlVersion, final int userId) {} private void loadAllowedComponentsFromSettings() { - - UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - for (UserInfo user : userManager.getUsers()) { + for (UserInfo user : mUm.getUsers()) { final ContentResolver cr = mContext.getContentResolver(); addApprovedList(Settings.Secure.getStringForUser( cr, @@ -482,7 +480,9 @@ abstract public class ManagedServices { for (int i = 0; i < allowedByType.size(); i++) { final ArraySet<String> allowed = allowedByType.valueAt(i); allowedPackages.addAll( - allowed.stream().map(this::getPackageName).collect(Collectors.toList())); + allowed.stream().map(this::getPackageName). + filter(value -> !TextUtils.isEmpty(value)) + .collect(Collectors.toList())); } return allowedPackages; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e7eed03a5bde..ada002c0c5b0 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -25,7 +25,9 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_NULL; +import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService .HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; @@ -88,6 +90,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -115,6 +118,7 @@ import android.os.IDeviceIdleController; import android.os.IInterface; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -206,6 +210,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -288,6 +293,7 @@ public class NotificationManagerService extends SystemService { private ICompanionDeviceManager mCompanionManager; private AccessibilityManager mAccessibilityManager; private IDeviceIdleController mDeviceIdleController; + private IBinder mPermissionOwner; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -524,7 +530,7 @@ public class NotificationManagerService extends SystemService { } catch (FileNotFoundException e) { // No data yet // Load default managed services approvals - readDefaultApprovedServices(UserHandle.USER_SYSTEM); + readDefaultApprovedServices(USER_SYSTEM); } catch (IOException e) { Log.wtf(TAG, "Unable to read notification policy", e); } catch (NumberFormatException e) { @@ -974,7 +980,7 @@ public class NotificationManagerService extends SystemService { final int enabled = mPackageManager.getApplicationEnabledSetting( pkgName, changeUserId != UserHandle.USER_ALL ? changeUserId : - UserHandle.USER_SYSTEM); + USER_SYSTEM); if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { cancelNotifications = false; @@ -1061,6 +1067,7 @@ public class NotificationManagerService extends SystemService { } } else if (action.equals(Intent.ACTION_USER_REMOVED)) { final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mUserProfiles.updateCache(context); mZenModeHelper.onUserRemoved(user); mRankingHelper.onUserRemoved(user); mListeners.onUserRemoved(user); @@ -1268,7 +1275,7 @@ public class NotificationManagerService extends SystemService { NotificationAssistants notificationAssistants, ConditionProviders conditionProviders, ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, - ActivityManager activityManager, GroupHelper groupHelper) { + ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, @@ -1276,7 +1283,7 @@ public class NotificationManagerService extends SystemService { mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - mAm = ActivityManager.getService(); + mAm = am; mPackageManager = packageManager; mPackageManagerClient = packageManagerClient; mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); @@ -1287,6 +1294,11 @@ public class NotificationManagerService extends SystemService { mActivityManager = activityManager; mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + try { + mPermissionOwner = mAm.newUriPermissionOwner("notification"); + } catch (RemoteException e) { + Slog.w(TAG, "AM dead", e); + } mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1415,7 +1427,7 @@ public class NotificationManagerService extends SystemService { null, snoozeHelper, new NotificationUsageStats(getContext()), new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), - getGroupHelper()); + getGroupHelper(), ActivityManager.getService()); // register for various Intents IntentFilter filter = new IntentFilter(); @@ -1749,7 +1761,7 @@ public class NotificationManagerService extends SystemService { protected void reportSeen(NotificationRecord r) { final int userId = r.sbn.getUserId(); mAppUsageStats.reportEvent(r.sbn.getPackageName(), - userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM + userId == UserHandle.USER_ALL ? USER_SYSTEM : userId, UsageEvents.Event.NOTIFICATION_SEEN); } @@ -2894,7 +2906,7 @@ public class NotificationManagerService extends SystemService { checkCallerIsSystem(); if (DBG) Slog.d(TAG, "getBackupPayload u=" + user); //TODO: http://b/22388012 - if (user != UserHandle.USER_SYSTEM) { + if (user != USER_SYSTEM) { Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user); return null; } @@ -2920,7 +2932,7 @@ public class NotificationManagerService extends SystemService { return; } //TODO: http://b/22388012 - if (user != UserHandle.USER_SYSTEM) { + if (user != USER_SYSTEM) { Slog.w(TAG, "applyRestore: cannot restore policy for user " + user); return; } @@ -3678,7 +3690,7 @@ public class NotificationManagerService extends SystemService { sbn.getNotification().flags = (r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE); mRankingHelper.sort(mNotificationList); - mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */); + mListeners.notifyPostedLocked(r, sbn /* oldSbn */); } }; @@ -3707,7 +3719,7 @@ public class NotificationManagerService extends SystemService { try { final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId); + (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); Notification.addFieldsFromContext(ai, notification); int canColorize = mPackageManagerClient.checkPermission( @@ -4126,6 +4138,8 @@ public class NotificationManagerService extends SystemService { // Make sure we don't lose the foreground service state. notification.flags |= old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; + // revoke uri permissions for changed uris + revokeUriPermissions(r, old); r.isUpdate = true; } @@ -4147,7 +4161,7 @@ public class NotificationManagerService extends SystemService { if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.sbn : null; - mListeners.notifyPostedLocked(n, oldSbn); + mListeners.notifyPostedLocked(r, oldSbn); if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) { mHandler.post(new Runnable() { @Override @@ -4912,6 +4926,9 @@ public class NotificationManagerService extends SystemService { r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER); } + // Revoke permissions + revokeUriPermissions(null, r); + // tell the app if (sendDelete) { if (r.getNotification().deleteIntent != null) { @@ -5009,6 +5026,30 @@ public class NotificationManagerService extends SystemService { r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), listenerName); } + void revokeUriPermissions(NotificationRecord newRecord, NotificationRecord oldRecord) { + Set<Uri> oldUris = oldRecord.getNotificationUris(); + Set<Uri> newUris = newRecord == null ? new HashSet<>() : newRecord.getNotificationUris(); + oldUris.removeAll(newUris); + + long ident = Binder.clearCallingIdentity(); + try { + for (Uri uri : oldUris) { + if (uri != null) { + int notiUserId = oldRecord.getUserId(); + int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM + : ContentProvider.getUserIdFromUri(uri, notiUserId); + uri = ContentProvider.getUriWithoutUserId(uri); + mAm.revokeUriPermissionFromOwner(mPermissionOwner, + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Count not revoke uri permissions", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} * and none of the {@code mustNotHaveFlags}. @@ -5851,10 +5892,13 @@ public class NotificationManagerService extends SystemService { * but isn't anymore. */ @GuardedBy("mNotificationLock") - public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) { + public void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn) { // Lazily initialized snapshots of the notification. + StatusBarNotification sbn = r.sbn; TrimCache trimCache = new TrimCache(sbn); + Set<Uri> uris = r.getNotificationUris(); + for (final ManagedServiceInfo info : getServices()) { boolean sbnVisible = isVisibleToListener(sbn, info); boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false; @@ -5877,6 +5921,9 @@ public class NotificationManagerService extends SystemService { continue; } + grantUriPermissions(uris, sbn.getUserId(), info.component.getPackageName(), + info.userid); + final StatusBarNotification sbnToPost = trimCache.ForListener(info); mHandler.post(new Runnable() { @Override @@ -5887,6 +5934,28 @@ public class NotificationManagerService extends SystemService { } } + private void grantUriPermissions(Set<Uri> uris, int notiUserId, String listenerPkg, + int listenerUserId) { + long ident = Binder.clearCallingIdentity(); + try { + for (Uri uri : uris) { + if (uri != null) { + int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM + : ContentProvider.getUserIdFromUri(uri, notiUserId); + uri = ContentProvider.getUriWithoutUserId(uri); + mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), + listenerPkg, + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, + listenerUserId == USER_ALL ? USER_SYSTEM : listenerUserId); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Count not grant uri permission to " + listenerPkg, e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * asynchronously notify all listeners about a removed notification */ diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 23b9743aaf78..1ad8c74a831c 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -38,6 +38,7 @@ import android.metrics.LogMaker; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Parcelable; import android.os.UserHandle; import android.provider.Settings; import android.service.notification.Adjustment; @@ -47,6 +48,7 @@ import android.service.notification.NotificationStats; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; @@ -64,6 +66,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Holds data about notifications that should not be shared with the @@ -929,6 +932,42 @@ public final class NotificationRecord { mStats.setViewedSettings(); } + public Set<Uri> getNotificationUris() { + Notification notification = getNotification(); + Set<Uri> uris = new ArraySet<>(); + + if (notification.sound != null) { + uris.add(notification.sound); + } + if (notification.getChannelId() != null) { + NotificationChannel channel = getChannel(); + if (channel != null && channel.getSound() != null) { + uris.add(channel.getSound()); + } + } + if (notification.extras.containsKey(Notification.EXTRA_AUDIO_CONTENTS_URI)) { + uris.add(notification.extras.getParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI)); + } + if (notification.extras.containsKey(Notification.EXTRA_BACKGROUND_IMAGE_URI)) { + uris.add(notification.extras.getParcelable(Notification.EXTRA_BACKGROUND_IMAGE_URI)); + } + if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { + Parcelable[] newMessages = + notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); + List<Notification.MessagingStyle.Message> messages + = Notification.MessagingStyle.Message.getMessagesFromBundleArray(newMessages); + Parcelable[] histMessages = + notification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES); + messages.addAll( + Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages)); + for (Notification.MessagingStyle.Message message : messages) { + uris.add(message.getDataUri()); + } + } + + return uris; + } + public LogMaker getLogMaker(long now) { if (mLogMaker == null) { // initialize fields that only change on update (so a new record) diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 7600e81cd1c0..6e02db7b25ea 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -20,6 +20,8 @@ import static android.content.om.OverlayInfo.STATE_DISABLED; import static android.content.om.OverlayInfo.STATE_ENABLED; import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.content.om.OverlayInfo.STATE_NO_IDMAP; +import static android.content.om.OverlayInfo.STATE_OVERLAY_UPGRADING; +import static android.content.om.OverlayInfo.STATE_TARGET_UPGRADING; import static com.android.server.om.OverlayManagerService.DEBUG; import static com.android.server.om.OverlayManagerService.TAG; @@ -50,6 +52,10 @@ import java.util.Set; * @see OverlayManagerService */ final class OverlayManagerServiceImpl { + // Flags to use in conjunction with updateState. + private static final int FLAG_TARGET_IS_UPGRADING = 1<<0; + private static final int FLAG_OVERLAY_IS_UPGRADING = 1<<1; + private final PackageManagerHelper mPackageManager; private final IdmapManager mIdmapManager; private final OverlayManagerSettings mSettings; @@ -123,9 +129,7 @@ final class OverlayManagerServiceImpl { } try { - final PackageInfo targetPackage = - mPackageManager.getPackageInfo(overlayPackage.overlayTarget, newUserId); - updateState(targetPackage, overlayPackage, newUserId); + updateState(overlayPackage.overlayTarget, overlayPackage.packageName, newUserId, 0); } catch (OverlayManagerSettings.BadKeyException e) { Slog.e(TAG, "failed to update settings", e); mSettings.remove(overlayPackage.packageName, newUserId); @@ -168,8 +172,7 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId); } - final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); - if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) { + if (updateAllOverlaysForTarget(packageName, userId, 0)) { mListener.onOverlaysChanged(packageName, userId); } } @@ -179,18 +182,18 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId); } - final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); - if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) { + if (updateAllOverlaysForTarget(packageName, userId, 0)) { mListener.onOverlaysChanged(packageName, userId); } } void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) { if (DEBUG) { - Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId); + Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + + userId); } - if (updateAllOverlaysForTarget(packageName, userId, null)) { + if (updateAllOverlaysForTarget(packageName, userId, FLAG_TARGET_IS_UPGRADING)) { mListener.onOverlaysChanged(packageName, userId); } } @@ -200,8 +203,7 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageUpgraded packageName=" + packageName + " userId=" + userId); } - final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); - if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) { + if (updateAllOverlaysForTarget(packageName, userId, 0)) { mListener.onOverlaysChanged(packageName, userId); } } @@ -211,7 +213,7 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId); } - if (updateAllOverlaysForTarget(packageName, userId, null)) { + if (updateAllOverlaysForTarget(packageName, userId, 0)) { mListener.onOverlaysChanged(packageName, userId); } } @@ -219,20 +221,21 @@ final class OverlayManagerServiceImpl { /** * Returns true if the settings were modified for this target. */ - private boolean updateAllOverlaysForTarget(@NonNull final String packageName, final int userId, - @Nullable final PackageInfo targetPackage) { + private boolean updateAllOverlaysForTarget(@NonNull final String targetPackageName, + final int userId, final int flags) { boolean modified = false; - final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(packageName, userId); + final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(targetPackageName, userId); final int N = ois.size(); for (int i = 0; i < N; i++) { final OverlayInfo oi = ois.get(i); - final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, userId); + final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, + userId); if (overlayPackage == null) { modified |= mSettings.remove(oi.packageName, oi.userId); removeIdmapIfPossible(oi); } else { try { - modified |= updateState(targetPackage, overlayPackage, userId); + modified |= updateState(targetPackageName, oi.packageName, userId, flags); } catch (OverlayManagerSettings.BadKeyException e) { Slog.e(TAG, "failed to update settings", e); modified |= mSettings.remove(oi.packageName, userId); @@ -254,14 +257,11 @@ final class OverlayManagerServiceImpl { return; } - final PackageInfo targetPackage = - mPackageManager.getPackageInfo(overlayPackage.overlayTarget, userId); - mSettings.init(packageName, userId, overlayPackage.overlayTarget, overlayPackage.applicationInfo.getBaseCodePath(), overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority); try { - if (updateState(targetPackage, overlayPackage, userId)) { + if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) { mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId); } } catch (OverlayManagerSettings.BadKeyException e) { @@ -271,15 +271,64 @@ final class OverlayManagerServiceImpl { } void onOverlayPackageChanged(@NonNull final String packageName, final int userId) { - Slog.wtf(TAG, "onOverlayPackageChanged called, but only pre-installed overlays supported"); + if (DEBUG) { + Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId); + } + + try { + final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); + if (updateState(oi.targetPackageName, packageName, userId, 0)) { + mListener.onOverlaysChanged(oi.targetPackageName, userId); + } + } catch (OverlayManagerSettings.BadKeyException e) { + Slog.e(TAG, "failed to update settings", e); + } } void onOverlayPackageUpgrading(@NonNull final String packageName, final int userId) { - Slog.wtf(TAG, "onOverlayPackageUpgrading called, but only pre-installed overlays supported"); + if (DEBUG) { + Slog.d(TAG, "onOverlayPackageUpgrading packageName=" + packageName + " userId=" + + userId); + } + + try { + final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); + if (updateState(oi.targetPackageName, packageName, userId, FLAG_OVERLAY_IS_UPGRADING)) { + removeIdmapIfPossible(oi); + mListener.onOverlaysChanged(oi.targetPackageName, userId); + } + } catch (OverlayManagerSettings.BadKeyException e) { + Slog.e(TAG, "failed to update settings", e); + } } void onOverlayPackageUpgraded(@NonNull final String packageName, final int userId) { - Slog.wtf(TAG, "onOverlayPackageUpgraded called, but only pre-installed overlays supported"); + if (DEBUG) { + Slog.d(TAG, "onOverlayPackageUpgraded packageName=" + packageName + " userId=" + + userId); + } + + final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId); + if (pkg == null) { + Slog.w(TAG, "overlay package " + packageName + " was upgraded, but couldn't be found"); + onOverlayPackageRemoved(packageName, userId); + return; + } + + try { + final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId); + if (!oldOi.targetPackageName.equals(pkg.overlayTarget)) { + mSettings.init(packageName, userId, pkg.overlayTarget, + pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(), + pkg.overlayPriority); + } + + if (updateState(pkg.overlayTarget, packageName, userId, 0)) { + mListener.onOverlaysChanged(pkg.overlayTarget, userId); + } + } catch (OverlayManagerSettings.BadKeyException e) { + Slog.e(TAG, "failed to update settings", e); + } } void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) { @@ -333,10 +382,8 @@ final class OverlayManagerServiceImpl { try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); - final PackageInfo targetPackage = - mPackageManager.getPackageInfo(oi.targetPackageName, userId); boolean modified = mSettings.setEnabled(packageName, userId, enable); - modified |= updateState(targetPackage, overlayPackage, userId); + modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0); if (modified) { mListener.onOverlaysChanged(oi.targetPackageName, userId); @@ -349,7 +396,8 @@ final class OverlayManagerServiceImpl { boolean setEnabledExclusive(@NonNull final String packageName, final int userId) { if (DEBUG) { - Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName, userId)); + Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName, + userId)); } final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); @@ -359,10 +407,9 @@ final class OverlayManagerServiceImpl { try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); - final PackageInfo targetPackage = - mPackageManager.getPackageInfo(oi.targetPackageName, userId); + final String targetPackageName = oi.targetPackageName; - List<OverlayInfo> allOverlays = getOverlayInfosForTarget(oi.targetPackageName, userId); + List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId); boolean modified = false; @@ -384,15 +431,15 @@ final class OverlayManagerServiceImpl { // Disable the overlay. modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false); - modified |= updateState(targetPackage, disabledOverlayPackageInfo, userId); + modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0); } // Enable the selected overlay. modified |= mSettings.setEnabled(packageName, userId, true); - modified |= updateState(targetPackage, overlayPackage, userId); + modified |= updateState(targetPackageName, packageName, userId, 0); if (modified) { - mListener.onOverlaysChanged(oi.targetPackageName, userId); + mListener.onOverlaysChanged(targetPackageName, userId); } return true; } catch (OverlayManagerSettings.BadKeyException e) { @@ -477,7 +524,8 @@ final class OverlayManagerServiceImpl { List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName, final int userId) { - final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, userId); + final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, + userId); final List<String> paths = new ArrayList<>(overlays.size()); final int N = overlays.size(); for (int i = 0; i < N; i++) { @@ -492,36 +540,59 @@ final class OverlayManagerServiceImpl { /** * Returns true if the settings/state was modified, false otherwise. */ - private boolean updateState(@Nullable final PackageInfo targetPackage, - @NonNull final PackageInfo overlayPackage, final int userId) + private boolean updateState(@NonNull final String targetPackageName, + @NonNull final String overlayPackageName, final int userId, final int flags) throws OverlayManagerSettings.BadKeyException { + + final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId); + final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName, + userId); + // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers. - if (targetPackage != null && - !("android".equals(targetPackage.packageName) + if (targetPackage != null && overlayPackage != null && + !("android".equals(targetPackageName) && overlayPackage.isStaticOverlayPackage())) { mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } - boolean modified = mSettings.setBaseCodePath(overlayPackage.packageName, userId, - overlayPackage.applicationInfo.getBaseCodePath()); + boolean modified = false; + if (overlayPackage != null) { + modified |= mSettings.setBaseCodePath(overlayPackageName, userId, + overlayPackage.applicationInfo.getBaseCodePath()); + } - final int currentState = mSettings.getState(overlayPackage.packageName, userId); - final int newState = calculateNewState(targetPackage, overlayPackage, userId); + final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId); + final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage, + userId, flags); if (currentState != newState) { if (DEBUG) { Slog.d(TAG, String.format("%s:%d: %s -> %s", - overlayPackage.packageName, userId, + overlayPackageName, userId, OverlayInfo.stateToString(currentState), OverlayInfo.stateToString(newState))); } - modified |= mSettings.setState(overlayPackage.packageName, userId, newState); + modified |= mSettings.setState(overlayPackageName, userId, newState); } return modified; } - private int calculateNewState(@Nullable final PackageInfo targetPackage, - @NonNull final PackageInfo overlayPackage, final int userId) + private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage, + @Nullable final PackageInfo overlayPackage, final int userId, final int flags) throws OverlayManagerSettings.BadKeyException { + + if ((flags & FLAG_TARGET_IS_UPGRADING) != 0) { + return STATE_TARGET_UPGRADING; + } + + if ((flags & FLAG_OVERLAY_IS_UPGRADING) != 0) { + return STATE_OVERLAY_UPGRADING; + } + + // assert expectation on overlay package: can only be null if the flags are used + if (DEBUG && overlayPackage == null) { + throw new IllegalArgumentException("null overlay package not compatible with no flags"); + } + if (targetPackage == null) { return STATE_MISSING_TARGET; } diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index 17b38deb27c1..a80cae4dcb4b 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -145,7 +145,8 @@ final class OverlayManagerSettings { return mItems.get(idx).setEnabled(enable); } - int getState(@NonNull final String packageName, final int userId) throws BadKeyException { + @OverlayInfo.State int getState(@NonNull final String packageName, final int userId) + throws BadKeyException { final int idx = select(packageName, userId); if (idx < 0) { throw new BadKeyException(packageName, userId); @@ -156,8 +157,8 @@ final class OverlayManagerSettings { /** * Returns true if the settings were modified, false if they remain the same. */ - boolean setState(@NonNull final String packageName, final int userId, final int state) - throws BadKeyException { + boolean setState(@NonNull final String packageName, final int userId, + final @OverlayInfo.State int state) throws BadKeyException { final int idx = select(packageName, userId); if (idx < 0) { throw new BadKeyException(packageName, userId); @@ -413,7 +414,7 @@ final class OverlayManagerSettings { private final String mPackageName; private final String mTargetPackageName; private String mBaseCodePath; - private int mState; + private @OverlayInfo.State int mState; private boolean mIsEnabled; private OverlayInfo mCache; private boolean mIsStatic; @@ -421,7 +422,7 @@ final class OverlayManagerSettings { SettingsItem(@NonNull final String packageName, final int userId, @NonNull final String targetPackageName, @NonNull final String baseCodePath, - final int state, final boolean isEnabled, final boolean isStatic, + final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic, final int priority) { mPackageName = packageName; mUserId = userId; @@ -462,11 +463,11 @@ final class OverlayManagerSettings { return false; } - private int getState() { + private @OverlayInfo.State int getState() { return mState; } - private boolean setState(final int state) { + private boolean setState(final @OverlayInfo.State int state) { if (mState != state) { mState = state; invalidateCache(); diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index af446ba02914..bc9fa4b7e32b 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -126,17 +126,16 @@ public abstract class InstantAppResolver { final Intent origIntent = requestObj.origIntent; final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); - final int[] shaPrefix = digest.getDigestPrefix(); AuxiliaryResolveInfo resolveInfo = null; @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; try { final List<InstantAppResolveInfo> instantAppResolveInfoList = - connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token); + connection.getInstantAppResolveInfoList(sanitizedIntent, + requestObj.digest.getDigestPrefixSecure(), token); if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { resolveInfo = InstantAppResolver.filterInstantAppIntent( instantAppResolveInfoList, origIntent, requestObj.resolvedType, - requestObj.userId, origIntent.getPackage(), digest, token); + requestObj.userId, origIntent.getPackage(), requestObj.digest, token); } } catch (ConnectionException e) { if (e.failure == ConnectionException.FAILURE_BIND) { @@ -166,12 +165,6 @@ public abstract class InstantAppResolver { return resolveInfo; } - private static InstantAppDigest getInstantAppDigest(Intent origIntent) { - return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost()) - ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/) - : InstantAppDigest.UNDEFINED; - } - public static void doInstantAppResolutionPhaseTwo(Context context, InstantAppResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { @@ -182,8 +175,6 @@ public abstract class InstantAppResolver { } final Intent origIntent = requestObj.origIntent; final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); - final int[] shaPrefix = digest.getDigestPrefix(); final PhaseTwoCallback callback = new PhaseTwoCallback() { @Override @@ -194,7 +185,8 @@ public abstract class InstantAppResolver { final AuxiliaryResolveInfo instantAppIntentInfo = InstantAppResolver.filterInstantAppIntent( instantAppResolveInfoList, origIntent, null /*resolvedType*/, - 0 /*userId*/, origIntent.getPackage(), digest, token); + 0 /*userId*/, origIntent.getPackage(), requestObj.digest, + token); if (instantAppIntentInfo != null) { failureIntent = instantAppIntentInfo.failureIntent; } else { @@ -225,8 +217,9 @@ public abstract class InstantAppResolver { } }; try { - connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback, - callbackHandler, startTime); + connection.getInstantAppIntentFilterList(sanitizedIntent, + requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler, + startTime); } catch (ConnectionException e) { @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; if (e.failure == ConnectionException.FAILURE_BIND) { @@ -377,7 +370,7 @@ public abstract class InstantAppResolver { failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); failureIntent.setLaunchToken(token); ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null; - boolean isWebIntent = origIntent.isBrowsableWebIntent(); + boolean isWebIntent = origIntent.isWebIntent(); for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) { if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) { Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest" @@ -448,7 +441,7 @@ public abstract class InstantAppResolver { instantAppInfo.getIntentFilters(); if (instantAppFilters == null || instantAppFilters.isEmpty()) { // No filters on web intent; no matches, 2nd phase unnecessary. - if (origIntent.isBrowsableWebIntent()) { + if (origIntent.isWebIntent()) { return null; } // No filters; we need to start phase two diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 81663321881c..2816bbd36b0b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5972,7 +5972,7 @@ public class PackageManagerService extends IPackageManager.Stub if (!skipPackageCheck && intent.getPackage() != null) { return false; } - if (!intent.isBrowsableWebIntent()) { + if (!intent.isWebIntent()) { // for non web intents, we should not resolve externally if an app already exists to // handle it or if the caller didn't explicitly request it. if ((resolvedActivities != null && resolvedActivities.size() != 0) @@ -6683,7 +6683,7 @@ public class PackageManagerService extends IPackageManager.Stub ai.packageName, ai.versionCode, null /* splitName */); } } - if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) { + if (intent.isWebIntent() && auxiliaryResponse == null) { return result; } final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 842f8d0a42f5..23185d7fb5ab 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -122,7 +122,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CONFIG_BRIGHTNESS, UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, UserManager.DISALLOW_AMBIENT_DISPLAY, - UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT + UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, + UserManager.DISALLOW_PRINTING }); /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 177d6af489b3..0502848d698e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5603,7 +5603,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int fl = PolicyControl.getWindowFlags(null, mTopFullscreenOpaqueWindowState.getAttrs()); if (localLOGV) { - Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()); + Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() + + " shown position: " + + mTopFullscreenOpaqueWindowState.getShownPositionLw()); Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs() + " lp.flags=0x" + Integer.toHexString(fl)); } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 3af3fcbbf7a8..e9c4c5c8138f 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -232,6 +232,14 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public Rect getFrameLw(); /** + * Retrieve the current position of the window that is actually shown. + * Must be called with the window manager lock held. + * + * @return Point The point holding the shown window position. + */ + public Point getShownPositionLw(); + + /** * Retrieve the frame of the display that this window was last * laid out in. Must be called with the * window manager lock held. diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index a53627093df6..3072f212d7c1 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -85,6 +85,7 @@ final class Notifier { private static final int MSG_WIRELESS_CHARGING_STARTED = 3; private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4; private static final int MSG_PROFILE_TIMED_OUT = 5; + private static final int MSG_WIRED_CHARGING_STARTED = 6; private final Object mLock = new Object(); @@ -571,6 +572,20 @@ final class Notifier { mHandler.sendMessage(msg); } + /** + * Called when wired charging has started so as to provide user feedback + */ + public void onWiredChargingStarted() { + if (DEBUG) { + Slog.d(TAG, "onWiredChargingStarted"); + } + + mSuspendBlocker.acquire(); + Message msg = mHandler.obtainMessage(MSG_WIRED_CHARGING_STARTED); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + private void updatePendingBroadcastLocked() { if (!mBroadcastInProgress && mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN @@ -703,11 +718,14 @@ final class Notifier { } }; - private void playWirelessChargingStartedSound() { + /** + * Plays the wireless charging sound for both wireless and non-wireless charging + */ + private void playChargingStartedSound() { final boolean enabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CHARGING_SOUNDS_ENABLED, 1) != 0; final String soundPath = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.WIRELESS_CHARGING_STARTED_SOUND); + Settings.Global.CHARGING_STARTED_SOUND); if (enabled && soundPath != null) { final Uri soundUri = Uri.parse("file://" + soundPath); if (soundUri != null) { @@ -721,11 +739,16 @@ final class Notifier { } private void showWirelessChargingStarted(int batteryLevel) { - playWirelessChargingStartedSound(); + playChargingStartedSound(); mStatusBarManagerInternal.showChargingAnimation(batteryLevel); mSuspendBlocker.release(); } + private void showWiredChargingStarted() { + playChargingStartedSound(); + mSuspendBlocker.release(); + } + private void lockProfile(@UserIdInt int userId) { mTrustManager.setDeviceLockedForUser(userId, true /*locked*/); } @@ -753,6 +776,8 @@ final class Notifier { case MSG_PROFILE_TIMED_OUT: lockProfile(msg.arg1); break; + case MSG_WIRED_CHARGING_STARTED: + showWiredChargingStarted(); } } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index db831581c791..1bb85c4426e1 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -1747,6 +1747,10 @@ public final class PowerManagerService extends SystemService // it can provide feedback to the user. if (dockedOnWirelessCharger || DEBUG_WIRELESS) { mNotifier.onWirelessChargingStarted(mBatteryLevel); + } else if (mIsPowered && !wasPowered + && (mPlugType == BatteryManager.BATTERY_PLUGGED_AC + || mPlugType == BatteryManager.BATTERY_PLUGGED_USB)) { + mNotifier.onWiredChargingStarted(); } } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index c280739f2e1a..a87ae1e4d4a0 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -284,7 +284,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private final static class AnomalyAlarmReceiver extends BroadcastReceiver { + public final static class AnomalyAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred."); @@ -304,7 +304,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private final static class PullingAlarmReceiver extends BroadcastReceiver { + public final static class PullingAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 5bc739ee33b2..925199358b4e 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -96,17 +96,13 @@ class RemoteAnimationController { // Scale the timeout with the animator scale the controlling app is using. mHandler.postDelayed(mTimeoutRunnable, (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale())); - - final RemoteAnimationTarget[] animations = createAnimations(); - mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { - try { - mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, - mFinishedCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to start remote animation", e); - onAnimationFinished(); - } - }); + try { + mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(), + mFinishedCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to start remote animation", e); + onAnimationFinished(); + } } private RemoteAnimationTarget[] createAnimations() { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8d1a82250206..c535fe5f7ddc 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -612,8 +612,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); } - mService.mAnimator.executeAfterPrepareSurfacesRunnables(); - final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked; // If we are ready to perform an app transition, check through all of the app tokens to be diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index da3a035ad8df..f2ad6fb7a888 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -272,8 +272,6 @@ class WallpaperController { } boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) { - int xOffset = 0; - int yOffset = 0; boolean rawChanged = false; // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to // match the behavior of most Launchers @@ -285,8 +283,11 @@ class WallpaperController { if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { offset += mLastWallpaperDisplayOffsetX; } - xOffset = offset; - + boolean changed = wallpaperWin.mXOffset != offset; + if (changed) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " x: " + offset); + wallpaperWin.mXOffset = offset; + } if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) { wallpaperWin.mWallpaperX = wpx; wallpaperWin.mWallpaperXStep = wpxs; @@ -300,16 +301,17 @@ class WallpaperController { if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { offset += mLastWallpaperDisplayOffsetY; } - yOffset = offset; - + if (wallpaperWin.mYOffset != offset) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset); + changed = true; + wallpaperWin.mYOffset = offset; + } if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) { wallpaperWin.mWallpaperY = wpy; wallpaperWin.mWallpaperYStep = wpys; rawChanged = true; } - boolean changed = wallpaperWin.mWinAnimator.setWallpaperOffset(xOffset, yOffset); - if (rawChanged && (wallpaperWin.mAttrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) { try { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index ddda027595da..2ae5c7bd9c25 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -74,6 +74,10 @@ class WallpaperWindowToken extends WindowToken { for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { final WindowState wallpaper = mChildren.get(wallpaperNdx); if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) { + final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; + winAnimator.computeShownFrameLocked(); + // No need to lay out the windows - we can just set the wallpaper position directly. + winAnimator.setWallpaperOffset(wallpaper.mShownPosition); // We only want to be synchronous with one wallpaper. sync = false; } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index b0d42f21fac3..cec13abd823d 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -92,7 +92,6 @@ public class WindowAnimator { * executed and the corresponding transaction is closed and applied. */ private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); - private boolean mInExecuteAfterPrepareSurfacesRunnables; WindowAnimator(final WindowManagerService service) { mService = service; @@ -439,13 +438,7 @@ public class WindowAnimator { scheduleAnimation(); } - void executeAfterPrepareSurfacesRunnables() { - - // Don't even think about to start recursing! - if (mInExecuteAfterPrepareSurfacesRunnables) { - return; - } - mInExecuteAfterPrepareSurfacesRunnables = true; + private void executeAfterPrepareSurfacesRunnables() { // Traverse in order they were added. final int size = mAfterPrepareSurfacesRunnables.size(); @@ -453,6 +446,5 @@ public class WindowAnimator { mAfterPrepareSurfacesRunnables.get(i).run(); } mAfterPrepareSurfacesRunnables.clear(); - mInExecuteAfterPrepareSurfacesRunnables = false; } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 48d29e372124..240e7fd8e9b0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -137,6 +137,7 @@ import static com.android.server.wm.proto.WindowStateProto.REMOVED; import static com.android.server.wm.proto.WindowStateProto.REMOVE_ON_EXIT; import static com.android.server.wm.proto.WindowStateProto.REQUESTED_HEIGHT; import static com.android.server.wm.proto.WindowStateProto.REQUESTED_WIDTH; +import static com.android.server.wm.proto.WindowStateProto.SHOWN_POSITION; import static com.android.server.wm.proto.WindowStateProto.STABLE_INSETS; import static com.android.server.wm.proto.WindowStateProto.STACK_ID; import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS; @@ -296,6 +297,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration(); /** + * Actual position of the surface shown on-screen (may be modified by animation). These are + * in the screen's coordinate space (WITH the compatibility scale applied). + */ + final Point mShownPosition = new Point(); + + /** * Insets that determine the actually visible area. These are in the application's * coordinate space (without compatibility scale applied). */ @@ -454,6 +461,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Wallpaper windows: pixels offset based on above variables. + int mXOffset; + int mYOffset; + /** * This is set after IWindowSession.relayout() has been called at * least once for the window. It allows us to detect the situation @@ -734,6 +745,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mRequestedHeight = 0; mLastRequestedWidth = 0; mLastRequestedHeight = 0; + mXOffset = 0; + mYOffset = 0; mLayer = 0; mInputWindowHandle = new InputWindowHandle( mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c, @@ -1099,6 +1112,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override + public Point getShownPositionLw() { + return mShownPosition; + } + + @Override public Rect getDisplayFrameLw() { return mDisplayFrame; } @@ -3116,6 +3134,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mContentInsets.writeToProto(proto, CONTENT_INSETS); mAttrs.surfaceInsets.writeToProto(proto, SURFACE_INSETS); mSurfacePosition.writeToProto(proto, SURFACE_POSITION); + mShownPosition.writeToProto(proto, SHOWN_POSITION); mWinAnimator.writeToProto(proto, ANIMATOR); proto.write(ANIMATING_EXIT, mAnimatingExit); for (int i = 0; i < mChildren.size(); i++) { @@ -3231,6 +3250,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled); pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded); } + if (mXOffset != 0 || mYOffset != 0) { + pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset); + pw.print(" y="); pw.println(mYOffset); + } if (dumpAll) { pw.print(prefix); pw.print("mGivenContentInsets="); mGivenContentInsets.printShortString(pw); @@ -3249,6 +3272,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(getLastReportedConfiguration()); } pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); + pw.print(" mShownPosition="); mShownPosition.printShortString(pw); pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay()); pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed); if (dumpAll) { @@ -4171,8 +4195,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int width = mFrame.width(); final int height = mFrame.height(); - final int left = mFrame.left; - final int top = mFrame.top; + // Compute the offset of the window in relation to the decor rect. + final int left = mXOffset + mFrame.left; + final int top = mYOffset + mFrame.top; // Initialize the decor rect to the entire frame. if (isDockedResizing()) { @@ -4367,8 +4392,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx; float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy; float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy; - int x = mSurfacePosition.x; - int y = mSurfacePosition.y; + int x = mSurfacePosition.x + mShownPosition.x; + int y = mSurfacePosition.y + mShownPosition.y; // If changed, also adjust transformFrameToSurfacePosition final WindowContainer parent = getParent(); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 499322c0b17a..dd23b6f25630 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -209,14 +209,10 @@ class WindowStateAnimator { float mExtraHScale = (float) 1.0; float mExtraVScale = (float) 1.0; - // An offset in pixel of the surface contents from the window position. Used for Wallpaper - // to provide the effect of scrolling within a large surface. We just use these values as - // a cache. - int mXOffset = 0; - int mYOffset = 0; - private final Rect mTmpSize = new Rect(); + private final SurfaceControl.Transaction mReparentTransaction = new SurfaceControl.Transaction(); + WindowStateAnimator(final WindowState win) { final WindowManagerService service = win.mService; @@ -377,9 +373,9 @@ class WindowStateAnimator { // child layers need to be reparented to the new surface to make this // transparent to the app. if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) { - SurfaceControl.openTransaction(); - mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController); - SurfaceControl.closeTransaction(); + mReparentTransaction.reparentChildren(mPendingDestroySurface.mSurfaceControl, + mSurfaceController.mSurfaceControl.getHandle()) + .apply(); } } } @@ -442,7 +438,7 @@ class WindowStateAnimator { flags |= SurfaceControl.SECURE; } - mTmpSize.set(0, 0, 0, 0); + mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0); calculateSurfaceBounds(w, attrs); final int width = mTmpSize.width(); final int height = mTmpSize.height(); @@ -593,7 +589,7 @@ class WindowStateAnimator { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { WindowManagerService.logSurface(mWin, "DESTROY PENDING", true); } - mPendingDestroySurface.destroyInTransaction(); + mPendingDestroySurface.destroyNotInTransaction(); } mPendingDestroySurface = mSurfaceController; } @@ -630,7 +626,7 @@ class WindowStateAnimator { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { WindowManagerService.logSurface(mWin, "DESTROY PENDING", true); } - mPendingDestroySurface.destroyInTransaction(); + mPendingDestroySurface.destroyNotInTransaction(); // Don't hide wallpaper if we're destroying a deferred surface // after a surface mode change. if (!mDestroyPreservedSurfaceUponRedraw) { @@ -683,8 +679,8 @@ class WindowStateAnimator { // WindowState.prepareSurfaces expands for surface insets (in order they don't get // clipped by the WindowState surface), so we need to go into the other direction here. - tmpMatrix.postTranslate(mWin.mAttrs.surfaceInsets.left, - mWin.mAttrs.surfaceInsets.top); + tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left, + mWin.mYOffset + mWin.mAttrs.surfaceInsets.top); // "convert" it into SurfaceFlinger's format @@ -699,6 +695,9 @@ class WindowStateAnimator { mDtDx = tmpFloats[Matrix.MSKEW_Y]; mDtDy = tmpFloats[Matrix.MSKEW_X]; mDsDy = tmpFloats[Matrix.MSCALE_Y]; + float x = tmpFloats[Matrix.MTRANS_X]; + float y = tmpFloats[Matrix.MTRANS_Y]; + mWin.mShownPosition.set(Math.round(x), Math.round(y)); // Now set the alpha... but because our current hardware // can't do alpha transformation on a non-opaque surface, @@ -708,7 +707,8 @@ class WindowStateAnimator { mShownAlpha = mAlpha; if (!mService.mLimitedAlphaCompositing || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format) - || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)))) { + || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy) + && x == frame.left && y == frame.top))) { //Slog.i(TAG_WM, "Applying alpha transform"); if (screenAnimation) { mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha(); @@ -738,6 +738,10 @@ class WindowStateAnimator { TAG, "computeShownFrameLocked: " + this + " not attached, mAlpha=" + mAlpha); + // WindowState.prepareSurfaces expands for surface insets (in order they don't get + // clipped by the WindowState surface), so we need to go into the other direction here. + mWin.mShownPosition.set(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left, + mWin.mYOffset + mWin.mAttrs.surfaceInsets.top); mShownAlpha = mAlpha; mHaveMatrix = false; mDsDx = mWin.mGlobalScale; @@ -788,6 +792,12 @@ class WindowStateAnimator { if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect + " fullscreen=" + fullscreen); + if (isFreeformResizing && !w.isChildWindow()) { + // For freeform resizing non child windows, we are using the big surface positioned + // at 0,0. Thus we must express the crop in that coordinate space. + clipRect.offset(w.mShownPosition.x, w.mShownPosition.y); + } + w.expandForSurfaceInsets(clipRect); // The clip rect was generated assuming (0,0) as the window origin, @@ -829,7 +839,7 @@ class WindowStateAnimator { return; } - mTmpSize.set(0, 0, 0, 0); + mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0); calculateSurfaceBounds(w, attrs); mExtraHScale = (float) 1.0; @@ -963,6 +973,11 @@ class WindowStateAnimator { // then take over the scaling until the new buffer arrives, and things // will be seamless. mForceScaleUntilResize = true; + } else { + if (!w.mSeamlesslyRotated) { + mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, + recoveringMemory); + } } // If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE @@ -1154,26 +1169,24 @@ class WindowStateAnimator { mSurfaceController.setTransparentRegionHint(region); } - boolean setWallpaperOffset(int dx, int dy) { - if (mXOffset == dx && mYOffset == dy) { - return false; - } - mXOffset = dx; - mYOffset = dy; + void setWallpaperOffset(Point shownPosition) { + final LayoutParams attrs = mWin.getAttrs(); + final int left = shownPosition.x - attrs.surfaceInsets.left; + final int top = shownPosition.y - attrs.surfaceInsets.top; try { if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset"); mService.openSurfaceTransaction(); - mSurfaceController.setPositionInTransaction(dx, dy, false); + mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left, + mWin.mFrame.top + top, false); applyCrop(null, false); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + mWin - + " pos=(" + dx + "," + dy + ")", e); + + " pos=(" + left + "," + top + ")", e); } finally { mService.closeSurfaceTransaction("setWallpaperOffset"); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setWallpaperOffset"); - return true; } } @@ -1408,7 +1421,7 @@ class WindowStateAnimator { void destroySurface() { try { if (mSurfaceController != null) { - mSurfaceController.destroyInTransaction(); + mSurfaceController.destroyNotInTransaction(); } } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying surface " + this diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 2f38556efa7c..554a60023aff 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -168,7 +168,7 @@ class WindowSurfaceController { } } - void destroyInTransaction() { + void destroyNotInTransaction() { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(8)); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index c4e485c1523a..3557dc90a503 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -122,16 +122,6 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public void setPrintingEnabled(ComponentName admin, boolean enabled) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isPrintingEnabled() { - return true; - } - - @Override public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) { return packageNames; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 4afe1c784701..953a79f625e6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -305,8 +305,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_PASSWORD_VALIDITY = "password-validity"; - private static final String TAG_PRINTING_ENABLED = "printing-enabled"; - private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle"; private static final int REQUEST_EXPIRE_PASSWORD = 5571; @@ -615,8 +613,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long mPasswordTokenHandle = 0; - boolean mPrintingEnabled = true; - public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -2948,12 +2944,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET); } - if (!policy.mPrintingEnabled) { - out.startTag(null, TAG_PRINTING_ENABLED); - out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mPrintingEnabled)); - out.endTag(null, TAG_PRINTING_ENABLED); - } - for (final String cert : policy.mOwnerInstalledCaCerts) { out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT); out.attribute(null, ATTR_ALIAS, cert); @@ -3172,9 +3162,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mCurrentInputMethodSet = true; } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) { policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS)); - } else if (TAG_PRINTING_ENABLED.equals(tag)) { - String enabled = parser.getAttributeValue(null, ATTR_VALUE); - policy.mPrintingEnabled = Boolean.toString(true).equals(enabled); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -10417,7 +10404,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId) { synchronized (DevicePolicyManagerService.this) { DevicePolicyData policy = getUserData(userId); - if (policy.mPrintingEnabled) { + if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING)) { Log.e(LOG_TAG, "printing is enabled"); return null; } @@ -12789,27 +12776,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private boolean hasPrinting() { - return mInjector.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING); - } - - @Override - public void setPrintingEnabled(ComponentName admin, boolean enabled) { - if (!mHasFeature || !hasPrinting()) { - return; - } - Preconditions.checkNotNull(admin, "Admin cannot be null."); - enforceProfileOrDeviceOwner(admin); - synchronized (this) { - final int userHandle = mInjector.userHandleGetCallingUserId(); - DevicePolicyData policy = getUserData(userHandle); - if (policy.mPrintingEnabled != enabled) { - policy.mPrintingEnabled = enabled; - saveSettingsLocked(userHandle); - } - } - } - private void deleteTransferOwnershipMetadataFileLocked() { mTransferOwnershipMetadataManager.deleteMetadataFile(); } @@ -12839,25 +12805,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - /** - * Returns whether printing is enabled for current user. - * @hide - */ - @Override - public boolean isPrintingEnabled() { - if (!hasPrinting()) { - return false; - } - if (!mHasFeature) { - return true; - } - synchronized (this) { - final int userHandle = mInjector.userHandleGetCallingUserId(); - DevicePolicyData policy = getUserData(userHandle); - return policy.mPrintingEnabled; - } - } - @Override public int addOverrideApn(@NonNull ComponentName who, @NonNull ApnSetting apnSetting) { if (!mHasFeature) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3210f1a2e8b9..c3614346bbdc 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -65,7 +65,6 @@ import com.android.server.am.ActivityManagerService; import com.android.server.audio.AudioService; import com.android.server.broadcastradio.BroadcastRadioService; import com.android.server.camera.CameraServiceProxy; -import com.android.server.car.CarServiceHelperService; import com.android.server.clipboard.ClipboardService; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.coverage.CoverageService; @@ -220,6 +219,8 @@ public final class SystemServer { "com.google.android.things.services.IoTSystemService"; private static final String SLICE_MANAGER_SERVICE_CLASS = "com.android.server.slice.SliceManagerService$Lifecycle"; + private static final String CAR_SERVICE_HELPER_SERVICE_CLASS = + "com.android.internal.car.CarServiceHelperService"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; @@ -1750,7 +1751,7 @@ public final class SystemServer { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { traceBeginAndSlog("StartCarServiceHelperService"); - mSystemServiceManager.startService(CarServiceHelperService.class); + mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS); traceEnd(); } diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index e1c1eb298e31..e8620edc9d5e 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -21,7 +21,6 @@ import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import android.annotation.NonNull; import android.app.ActivityManager; -import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; @@ -115,12 +114,9 @@ public final class PrintManagerService extends SystemService { private final SparseArray<UserState> mUserStates = new SparseArray<>(); - private final DevicePolicyManager mDpm; - PrintManagerImpl(Context context) { mContext = context; mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); registerContentObservers(); registerBroadcastReceivers(); } @@ -722,7 +718,7 @@ public final class PrintManagerService extends SystemService { } private boolean isPrintingEnabled() { - return mDpm == null || mDpm.isPrintingEnabled(); + return !mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING); } private void dump(@NonNull DualDumpOutputStream dumpStream, diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java index 2c50f2272187..a499472d197f 100644 --- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java @@ -343,6 +343,7 @@ public class ForceAppStandbyTrackerTest { assertTrue(instance.isUidActive(Process.SYSTEM_UID)); mIUidObserver.onUidActive(UID_1); + waitUntilMainHandlerDrain(); areRestricted(instance, UID_1, PACKAGE_1, NONE); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); @@ -350,6 +351,7 @@ public class ForceAppStandbyTrackerTest { assertFalse(instance.isUidActive(UID_2)); mIUidObserver.onUidGone(UID_1, /*disable=*/ false); + waitUntilMainHandlerDrain(); areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); @@ -357,11 +359,13 @@ public class ForceAppStandbyTrackerTest { assertFalse(instance.isUidActive(UID_2)); mIUidObserver.onUidActive(UID_1); + waitUntilMainHandlerDrain(); areRestricted(instance, UID_1, PACKAGE_1, NONE); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); mIUidObserver.onUidIdle(UID_1, /*disable=*/ false); + waitUntilMainHandlerDrain(); areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); @@ -467,6 +471,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidActive(UID_1); + waitUntilMainHandlerDrain(); assertTrue(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -479,6 +484,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidStateChanged(UID_2, ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0); + waitUntilMainHandlerDrain(); assertTrue(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -491,6 +497,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidStateChanged(UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0); + waitUntilMainHandlerDrain(); assertTrue(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -501,6 +508,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidGone(UID_1, true); + waitUntilMainHandlerDrain(); assertFalse(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -511,6 +519,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidIdle(UID_2, true); + waitUntilMainHandlerDrain(); assertFalse(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -522,6 +531,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidStateChanged(UID_1, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0); + waitUntilMainHandlerDrain(); assertFalse(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -533,6 +543,7 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidStateChanged(UID_1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0); + waitUntilMainHandlerDrain(); assertFalse(instance.isUidActive(UID_1)); assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); @@ -1037,6 +1048,8 @@ public class ForceAppStandbyTrackerTest { mIUidObserver.onUidActive(UID_1); mIUidObserver.onUidActive(UID_10_1); + waitUntilMainHandlerDrain(); + setAppOps(UID_2, PACKAGE_2, true); setAppOps(UID_10_2, PACKAGE_2, true); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index f9ffccdd5d38..0f0e3f386fec 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -255,6 +255,9 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); @@ -264,6 +267,56 @@ public class PlatformKeyManagerTest { } @Test + public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); // new version is available + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); + + verify(mKeyStoreProxy).containsAlias( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + any()); + } + + @Test + public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); + + verify(mKeyStoreProxy).containsAlias( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + any()); + } + + @Test public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), @@ -274,7 +327,13 @@ public class PlatformKeyManagerTest { + "platform/42/1/encrypt")).thenReturn(true); when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/2/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); @@ -295,11 +354,16 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed. - + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); // new version is available + + "platform/42/2/decrypt")).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); @@ -312,14 +376,70 @@ public class PlatformKeyManagerTest { } @Test - public void getEncryptKey_generatesNewKeyIfOldWasRemoved() throws Exception { + public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception { + doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + any()); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); // new version is available when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed. + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); + + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + any()); + } + @Test + public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(false); // was removed when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/2/encrypt")).thenReturn(true); // new version is available + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); + + verify(mKeyStoreProxy).containsAlias( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + any()); + } + + @Test + public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); @@ -332,10 +452,13 @@ public class PlatformKeyManagerTest { } @Test - public void getEncryptKey_getsEndryptKeyWithCorrectAlias() throws Exception { + public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 473a813c3838..2343deec020b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -125,6 +125,7 @@ public class RecoverableKeyStoreManagerTest { private static final byte[] RECOVERY_RESPONSE_HEADER = "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final String TEST_ALIAS = "nick"; + private static final String TEST_ALIAS2 = "bob"; private static final int RECOVERABLE_KEY_SIZE_BYTES = 32; private static final int GENERATION_ID = 1; private static final byte[] NONCE = getUtf8Bytes("nonce"); @@ -424,7 +425,7 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception { + public void recoverKeys_throwsIfFailedToDecryptAllApplicationKeys() throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, @@ -442,7 +443,7 @@ public class RecoverableKeyStoreManagerTest { keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); WrappedApplicationKey badApplicationKey = new WrappedApplicationKey( TEST_ALIAS, - randomBytes(32)); + encryptedApplicationKey(randomRecoveryKey(), randomBytes(32))); try { mRecoverableKeyStoreManager.recoverKeys( @@ -451,7 +452,7 @@ public class RecoverableKeyStoreManagerTest { /*applicationKeys=*/ ImmutableList.of(badApplicationKey)); fail("should have thrown"); } catch (ServiceSpecificException e) { - assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'"); + assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys"); } } @@ -487,6 +488,44 @@ public class RecoverableKeyStoreManagerTest { } @Test + public void recoverKeys_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyChainProtectionParams( + TYPE_LOCKSCREEN, + UI_FORMAT_PASSWORD, + KeyDerivationParams.createSha256Params(TEST_SALT), + TEST_SECRET))); + byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) + .getKeyClaimant(); + SecretKey recoveryKey = randomRecoveryKey(); + byte[] encryptedClaimResponse = encryptClaimResponse( + keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); + + byte[] applicationKeyBytes1 = randomBytes(32); + byte[] applicationKeyBytes2 = randomBytes(32); + + WrappedApplicationKey applicationKey1 = new WrappedApplicationKey( + TEST_ALIAS, + // Use a different recovery key here, so the decryption will fail + encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1)); + WrappedApplicationKey applicationKey2 = new WrappedApplicationKey( + TEST_ALIAS2, + encryptedApplicationKey(recoveryKey, applicationKeyBytes2)); + + Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + encryptedClaimResponse, + ImmutableList.of(applicationKey1, applicationKey2)); + + assertThat(recoveredKeys).hasSize(1); + assertThat(recoveredKeys.get(TEST_ALIAS2)).isEqualTo(applicationKeyBytes2); + } + + @Test public void setSnapshotCreatedPendingIntent() throws Exception { int uid = Binder.getCallingUid(); PendingIntent intent = PendingIntent.getBroadcast( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java index ac6d29395a85..d08dab4752d5 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java @@ -146,14 +146,14 @@ public final class CertUtilsTest { @Test public void getXmlNodeContents_singleLevel_succeeds() throws Exception { Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); - assertThat(CertUtils.getXmlNodeContents(CertUtils.MustExist.FALSE, root, "node1")) + assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node1")) .containsExactly("node1-1", "node1-2"); } @Test public void getXmlNodeContents_multipleLevels_succeeds() throws Exception { Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); - assertThat(CertUtils.getXmlNodeContents(CertUtils.MustExist.FALSE, root, "node2", "node1")) + assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node1")) .containsExactly("node2-node1-1", "node2-node1-2", "node2-node1-3"); } @@ -162,7 +162,7 @@ public final class CertUtilsTest { Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); assertThat( CertUtils.getXmlNodeContents( - CertUtils.MustExist.FALSE, root, "node2", "node-not-exist")) + CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node-not-exist")) .isEmpty(); } @@ -174,7 +174,7 @@ public final class CertUtilsTest { CertParsingException.class, () -> CertUtils.getXmlNodeContents( - CertUtils.MustExist.AT_LEAST_ONE, root, "node2", + CertUtils.MUST_EXIST_AT_LEAST_ONE, root, "node2", "node-not-exist")); assertThat(expected.getMessage()).contains("must contain at least one"); } @@ -187,7 +187,7 @@ public final class CertUtilsTest { CertParsingException.class, () -> CertUtils.getXmlNodeContents( - CertUtils.MustExist.EXACTLY_ONE, root, "node-not-exist", + CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node-not-exist", "node1")); assertThat(expected.getMessage()).contains("must contain exactly one"); } @@ -200,7 +200,7 @@ public final class CertUtilsTest { CertParsingException.class, () -> CertUtils.getXmlNodeContents( - CertUtils.MustExist.EXACTLY_ONE, root, "node2", "node1")); + CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node2", "node1")); assertThat(expected.getMessage()).contains("must contain exactly one"); } diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java index 5de393c7ae2b..a628b7b70c15 100644 --- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java +++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java @@ -79,6 +79,11 @@ public class FakeWindowState implements WindowManagerPolicy.WindowState { } @Override + public Point getShownPositionLw() { + return new Point(parentFrame.left, parentFrame.top); + } + + @Override public Rect getDisplayFrameLw() { return displayFrame; } diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java index f23bd6209614..3a1485e9fd46 100644 --- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.graphics.Color.BLUE; import static android.graphics.Color.RED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; @@ -27,7 +26,6 @@ import static android.view.Gravity.LEFT; import static android.view.Gravity.RIGHT; import static android.view.Gravity.TOP; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -49,6 +47,7 @@ import android.media.ImageReader; import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.Pair; @@ -66,18 +65,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.function.BooleanSupplier; /** * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests + * atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests */ // TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags. // TODO: Test non-Activity windows. @SmallTest -// TODO(b/68957554) -//@Presubmit +@Presubmit +@FlakyTest(bugId = 68957554) @RunWith(AndroidJUnit4.class) public class ScreenDecorWindowTests { @@ -123,40 +123,33 @@ public class ScreenDecorWindowTests { public void testScreenSides() throws Exception { // Decor on top final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); // Decor at the bottom updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness); // Decor to the left updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness); // Decor to the right updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetRight(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness); } @Test public void testMultipleDecors() throws Exception { // Test 2 decor windows on-top. createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mHalfDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness); createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); // And one at the bottom. createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); - assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mHalfDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness); } @Test @@ -164,18 +157,15 @@ public class ScreenDecorWindowTests { WindowInsets initialInsets = getInsets(mTestActivity); final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertEquals(mDecorThickness, insets.getSystemWindowInsetTop()); + assertTopInsetEquals(mTestActivity, mDecorThickness); updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, 0, PRIVATE_FLAG_IS_SCREEN_DECOR); - insets = getInsets(mTestActivity); - assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop()); + assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR); - insets = getInsets(mTestActivity); - assertEquals(mDecorThickness, insets.getSystemWindowInsetTop()); + assertTopInsetEquals(mTestActivity, mDecorThickness); } @Test @@ -183,17 +173,10 @@ public class ScreenDecorWindowTests { WindowInsets initialInsets = getInsets(mTestActivity); final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); removeWindow(decorWindow); - insets = getInsets(mTestActivity); - assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop()); - } - - private View createAppWindow() { - return createWindow("appWindow", TOP, MATCH_PARENT, MATCH_PARENT, BLUE, - FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR, 0); + assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); } private View createDecorWindow(int gravity, int width, int height) { @@ -249,10 +232,6 @@ public class ScreenDecorWindowTests { waitForIdle(); } - private WindowInsets getInsets(View v) { - return new WindowInsets(v.getRootWindowInsets()); - } - private WindowInsets getInsets(Activity a) { return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets()); } @@ -269,11 +248,59 @@ public class ScreenDecorWindowTests { lp.flags = (lp.flags & ~mask) | (flags & mask); } + /** + * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed. + */ + private void assertTopInsetEquals(Activity activity, int expected) throws Exception { + waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected); + assertEquals(expected, getInsets(activity).getSystemWindowInsetTop()); + } + + /** + * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected} + * waiting as needed. + */ + private void assertInsetGreaterOrEqual(Activity activity, int side, int expected) + throws Exception { + waitFor(() -> { + final WindowInsets insets = getInsets(activity); + switch (side) { + case TOP: return insets.getSystemWindowInsetTop() >= expected; + case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected; + case LEFT: return insets.getSystemWindowInsetLeft() >= expected; + case RIGHT: return insets.getSystemWindowInsetRight() >= expected; + default: return true; + } + }); + + final WindowInsets insets = getInsets(activity); + switch (side) { + case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break; + case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break; + case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break; + case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break; + } + } + /** Asserts that the first entry is greater than or equal to the second entry. */ private void assertGreaterOrEqual(int first, int second) throws Exception { Assert.assertTrue("Excepted " + first + " >= " + second, first >= second); } + private void waitFor(BooleanSupplier waitCondition) { + int retriesLeft = 5; + do { + if (waitCondition.getAsBoolean()) { + break; + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // Well I guess we are not waiting... + } + } while (retriesLeft-- > 0); + } + private void finishActivity(Activity a) { if (a == null) { return; 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 cd324259ce56..d6f61cd12765 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -38,6 +38,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.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -49,8 +50,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; +import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; @@ -64,9 +67,11 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.graphics.Color; import android.media.AudioManager; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.provider.Settings.Secure; @@ -149,6 +154,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock private ICompanionDeviceManager mCompanionMgr; @Mock SnoozeHelper mSnoozeHelper; @Mock GroupHelper mGroupHelper; + @Mock + IBinder mPermOwner; + @Mock + IActivityManager mAm; // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { @@ -208,6 +217,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class)); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); + when(mAm.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); // write to a test file; the system file isn't readable from tests mFile = new File(mContext.getCacheDir(), "test.xml"); @@ -238,7 +248,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, - mGroupHelper); + mGroupHelper, mAm); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; @@ -592,6 +602,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getActiveNotifications(PKG); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner( + any(), any(), anyInt(), anyInt()); } @Test @@ -610,6 +622,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class); verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture()); assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -624,6 +637,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getActiveNotifications(sbn.getPackageName()); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -637,6 +651,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getActiveNotifications(sbn.getPackageName()); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -658,6 +673,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class); verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture()); assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -676,6 +692,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId()); waitForIdle(); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -689,6 +706,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -2447,4 +2465,64 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getBackupPayload(1); assertEquals(1, mService.countSystemChecks - systemChecks); } + + @Test + public void revokeUriPermissions_update() throws Exception { + NotificationChannel c = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + Message message1 = new Message("", 0, ""); + message1.setData("", Uri.fromParts("old", "", "old stuff")); + Message message2 = new Message("", 1, ""); + message2.setData("", Uri.fromParts("new", "", "new stuff")); + + Notification.Builder nb = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("") + .addMessage(message1) + .addMessage(message2)); + StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord oldRecord = + new NotificationRecord(mContext, oldSbn, c); + + Notification.Builder nb1 = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("").addMessage(message2)); + StatusBarNotification newSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, + nb1.build(), new UserHandle(mUid), null, 0); + NotificationRecord newRecord = + new NotificationRecord(mContext, newSbn, c); + + mService.revokeUriPermissions(newRecord, oldRecord); + + verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()), + anyInt(), anyInt()); + } + + @Test + public void revokeUriPermissions_cancel() throws Exception { + NotificationChannel c = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + Message message1 = new Message("", 0, ""); + message1.setData("", Uri.fromParts("old", "", "old stuff")); + + Notification.Builder nb = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("") + .addMessage(message1)); + StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord oldRecord = + new NotificationRecord(mContext, oldSbn, c); + + mService.revokeUriPermissions(null, oldRecord); + + verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()), + anyInt(), anyInt()); + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 36a2a95dd1e1..3b0fd1f9d37c 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -654,7 +654,7 @@ public class UsageStatsService extends SystemService implements public boolean isAppInactive(String packageName, int userId) { try { userId = ActivityManager.getService().handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), userId, false, true, "isAppInactive", null); + Binder.getCallingUid(), userId, false, false, "isAppInactive", null); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java deleted file mode 100644 index b362df9ff677..000000000000 --- a/telephony/java/android/telephony/LocationAccessPolicy.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2017 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.telephony; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Build; -import android.os.Process; -import android.os.Trace; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.util.SparseBooleanArray; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Helper for performing location access checks. - * @hide - */ -public final class LocationAccessPolicy { - /** - * API to determine if the caller has permissions to get cell location. - * - * @param pkgName Package name of the application requesting access - * @param uid The uid of the package - * @param pid The pid of the package - * @return boolean true or false if permissions is granted - */ - public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, - int uid, int pid) throws SecurityException { - Trace.beginSection("TelephonyLocationCheck"); - try { - // Always allow the phone process to access location. This avoid breaking legacy code - // that rely on public-facing APIs to access cell location, and it doesn't create a - // info leak risk because the cell location is stored in the phone process anyway. - if (uid == Process.PHONE_UID) { - return true; - } - - // We always require the location permission and also require the - // location mode to be on for non-legacy apps. Legacy apps are - // required to be in the foreground to at least mitigate the case - // where a legacy app the user is not using tracks their location. - // Granting ACCESS_FINE_LOCATION to an app automatically grants it - // ACCESS_COARSE_LOCATION. - - if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) == - PackageManager.PERMISSION_DENIED) { - return false; - } - final int opCode = AppOpsManager.permissionToOpCode( - Manifest.permission.ACCESS_COARSE_LOCATION); - if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) - .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { - return false; - } - if (!isLocationModeEnabled(context, UserHandle.getUserId(uid)) - && !isLegacyForeground(context, pkgName, uid)) { - return false; - } - // If the user or profile is current, permission is granted. - // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); - } finally { - Trace.endSection(); - } - } - - private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { - int locationMode = Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId); - return locationMode != Settings.Secure.LOCATION_MODE_OFF - && locationMode != Settings.Secure.LOCATION_MODE_SENSORS_ONLY; - } - - private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName, - int uid) { - long token = Binder.clearCallingIdentity(); - try { - return isLegacyVersion(context, pkgName) && isForegroundApp(context, uid); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) { - try { - if (context.getPackageManager().getApplicationInfo(pkgName, 0) - .targetSdkVersion <= Build.VERSION_CODES.O) { - return true; - } - } catch (PackageManager.NameNotFoundException e) { - // In case of exception, assume known app (more strict checking) - // Note: This case will never happen since checkPackage is - // called to verify validity before checking app's version. - } - return false; - } - - private static boolean isForegroundApp(@NonNull Context context, int uid) { - final ActivityManager am = context.getSystemService(ActivityManager.class); - return am.getUidImportance(uid) <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; - } - - private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { - return context.checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - == PackageManager.PERMISSION_GRANTED; - } - - private static boolean isCurrentProfile(@NonNull Context context, int uid) { - long token = Binder.clearCallingIdentity(); - try { - final int currentUser = ActivityManager.getCurrentUser(); - final int callingUserId = UserHandle.getUserId(uid); - if (callingUserId == currentUser) { - return true; - } else { - List<UserInfo> userProfiles = context.getSystemService( - UserManager.class).getProfiles(currentUser); - for (UserInfo user : userProfiles) { - if (user.id == callingUserId) { - return true; - } - } - } - return false; - } finally { - Binder.restoreCallingIdentity(token); - } - } -} diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 34f2dac028c7..debf43da79b4 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -612,9 +612,9 @@ public class SubscriptionManager { * onSubscriptionsChanged overridden. */ public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { - String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { - logd("register OnSubscriptionsChangedListener pkgName=" + pkgName + logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + " listener=" + listener); } try { @@ -623,7 +623,7 @@ public class SubscriptionManager { ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( "telephony.registry")); if (tr != null) { - tr.addOnSubscriptionsChangedListener(pkgName, listener.callback); + tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback); } } catch (RemoteException ex) { // Should not happen diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 5c290da44e3d..03a8f330b9cb 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6587,11 +6587,48 @@ public class TelephonyManager { * @hide */ public void setBasebandVersionForPhone(int phoneId, String version) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_BASEBAND_VERSION, version); + } + + /** + * Get baseband version for the default phone. + * + * @return baseband version. + * @hide + */ + public String getBasebandVersion() { + int phoneId = getPhoneId(); + return getBasebandVersionForPhone(phoneId); + } + + /** + * Get baseband version for the default phone using the legacy approach. + * This change was added in P, to ensure backward compatiblity. + * + * @return baseband version. + * @hide + */ + private String getBasebandVersionLegacy(int phoneId) { if (SubscriptionManager.isValidPhoneId(phoneId)) { String prop = TelephonyProperties.PROPERTY_BASEBAND_VERSION + ((phoneId == 0) ? "" : Integer.toString(phoneId)); - SystemProperties.set(prop, version); + return SystemProperties.get(prop); + } + return null; + } + + /** + * Get baseband version by phone id. + * + * @return baseband version. + * @hide + */ + public String getBasebandVersionForPhone(int phoneId) { + String version = getBasebandVersionLegacy(phoneId); + if (version != null && !version.isEmpty()) { + setBasebandVersionForPhone(phoneId, version); } + return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_BASEBAND_VERSION, ""); } /** diff --git a/test-base/Android.bp b/test-base/Android.bp index 62fed61da276..b65cda9302a7 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -24,6 +24,9 @@ java_library { srcs: ["src/**/*.java"], + // Needs to be consistent with the repackaged version of this make target. + java_version: "1.8", + no_framework_libs: true, hostdex: true, libs: [ @@ -55,21 +58,22 @@ java_library_static { name: "repackaged.android.test.base", static_libs: ["android.test.base"], - no_framework_libs: true, libs: [ "framework", ], jarjar_rules: "jarjar-rules.txt", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", } // Build the android.test.base-minus-junit library // =============================================== // This contains the android.test classes from android.test.base plus // the com.android.internal.util.Predicate[s] classes. This is only -// intended for inclusion in the android.test.legacy static library and -// must not be used elsewhere. +// intended for inclusion in the android.test.legacy and +// legacy-android-test static libraries and must not be used elsewhere. java_library_static { name: "android.test.base-minus-junit", diff --git a/test-mock/Android.bp b/test-mock/Android.bp index b1ae40e17b9d..54e07a1673e7 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -19,6 +19,8 @@ java_library { name: "android.test.mock", + // Needs to be consistent with the repackaged version of this make target. + java_version: "1.8", srcs: ["src/**/*.java"], no_framework_libs: true, @@ -35,4 +37,6 @@ java_library_static { static_libs: ["android.test.mock"], jarjar_rules: "jarjar-rules.txt", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index dfaeed5e271e..66b95271ee06 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -19,6 +19,8 @@ java_library { name: "android.test.runner", + // Needs to be consistent with the repackaged version of this make target. + java_version: "1.8", srcs: ["src/**/*.java"], no_framework_libs: true, @@ -31,7 +33,8 @@ java_library { // Build the android.test.runner-minus-junit library // ================================================= -// This is provided solely for use by the legacy-android-test module. +// This is only intended for inclusion in the android.test.legacy and +// legacy-android-test static libraries and must not be used elsewhere. java_library { name: "android.test.runner-minus-junit", @@ -54,4 +57,6 @@ java_library_static { static_libs: ["android.test.runner"], jarjar_rules: "jarjar-rules.txt", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", } diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java index 7ea9ba3d05ee..1f061218a10c 100644 --- a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java +++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java @@ -17,16 +17,19 @@ package com.android.frameworks.perftests.amteststestapp; import android.app.Activity; -import android.os.Bundle; +import android.os.Looper; +import android.os.MessageQueue; import com.android.frameworks.perftests.am.util.Constants; import com.android.frameworks.perftests.am.util.Utils; public class TestActivity extends Activity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Utils.sendTime(getIntent(), Constants.TYPE_ACTIVITY_CREATED); + protected void onResume() { + super.onResume(); + Looper.myQueue().addIdleHandler(() -> { + Utils.sendTime(getIntent(), Constants.TYPE_TARGET_PACKAGE_START); + return false; + }); } } diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java index c86714156c2b..26a8e7b8945e 100644 --- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java +++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java @@ -62,7 +62,7 @@ public class TargetPackageUtils { sleep(); } // make sure Application has run - timeReceiver.getReceivedTimeNs(Constants.TYPE_ACTIVITY_CREATED); + timeReceiver.getReceivedTimeNs(Constants.TYPE_TARGET_PACKAGE_START); Utils.drainBroadcastQueue(); } diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java index 6528028ee1a1..f35c2fd38dbc 100644 --- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java +++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java @@ -17,7 +17,7 @@ package com.android.frameworks.perftests.am.util; public class Constants { - public static final String TYPE_ACTIVITY_CREATED = "activity_create"; + public static final String TYPE_TARGET_PACKAGE_START = "target_package_start"; public static final String TYPE_BROADCAST_RECEIVE = "broadcast_receive"; public static final String ACTION_BROADCAST_MANIFEST_RECEIVE = diff --git a/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java b/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java index d46fff4db65a..a7d5ae8f69a3 100644 --- a/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java +++ b/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java @@ -99,6 +99,21 @@ public class TonalTest { } @Test + public void tonal_rangeTest() { + Tonal.ConfigParser config = new Tonal.ConfigParser(InstrumentationRegistry.getContext()); + for (Tonal.TonalPalette palette : config.getTonalPalettes()) { + assertTrue("minHue should be >= to 0.", palette.minHue >= 0); + assertTrue("maxHue should be <= to 360.", palette.maxHue <= 360); + + assertTrue("S should be >= to 0.", palette.s[0] >= 0); + assertTrue("S should be <= to 1.", palette.s[1] <= 1); + + assertTrue("L should be >= to 0.", palette.l[0] >= 0); + assertTrue("L should be <= to 1.", palette.l[1] <= 1); + } + } + + @Test public void tonal_blacklistTest() { // Make sure that palette generation will fail. final Tonal tonal = new Tonal(InstrumentationRegistry.getContext()); diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index 9aad413c354b..04266c5b3a0f 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -172,7 +172,7 @@ public class MacAddressTest { final int iterations = 1000; final String expectedAndroidOui = "da:a1:19"; for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(); + MacAddress mac = MacAddress.createRandomUnicastAddressWithGoogleBase(); String stringRepr = mac.toString(); assertTrue(stringRepr + " expected to be a locally assigned address", @@ -195,6 +195,15 @@ public class MacAddressTest { assertTrue(stringRepr + " expected to begin with " + expectedLocalOui, stringRepr.startsWith(expectedLocalOui)); } + + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddress.createRandomUnicastAddress(); + String stringRepr = mac.toString(); + + assertTrue(stringRepr + " expected to be a locally assigned address", + mac.isLocallyAssigned()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); + } } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6e643a3dae12..e7abede4cda4 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -318,6 +318,7 @@ public class ConnectivityServiceTest { // This test has an inherent race condition in it, and cannot be enabled for continuous testing // or presubmit tests. It is kept for manual runs and documentation purposes. + @Ignore public void verifyThatNotWaitingForIdleCausesRaceConditions() { // Bring up a network that we can use to send messages to ConnectivityService. ConditionVariable cv = waitForConnectivityBroadcasts(1); diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt index f278aec8eb6f..d75aea507980 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt @@ -1,3 +1,19 @@ +/* + * 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.sdkparcelables import org.objectweb.asm.ClassVisitor @@ -25,4 +41,4 @@ class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) super.visit(version, access, name, signature, superName, interfaces) } -}
\ No newline at end of file +} diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt index 3e9d92cd978f..22e8d781335b 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -1,3 +1,19 @@ +/* + * 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.sdkparcelables import org.objectweb.asm.ClassReader @@ -53,4 +69,4 @@ fun main(args: Array<String>) { fun usage() { System.err.println("Usage: <input jar> <output aidl>") kotlin.system.exitProcess(1) -}
\ No newline at end of file +} diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt index 620f798daf48..d6a0a4516a6d 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt @@ -1,3 +1,19 @@ +/* + * 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.sdkparcelables /** A class that uses an ancestor map to find all classes that diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt index edfc8259a738..c9bcbc9cadcf 100644 --- a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt +++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt @@ -1,3 +1,19 @@ +/* + * 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.sdkparcelables import junit.framework.TestCase.assertEquals diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 71ca068e5cde..f3a78bd01cbe 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -611,6 +611,7 @@ public class WifiManagerTest { /** * Verify the watchLocalOnlyHotspot call goes to WifiServiceImpl. */ + @Test public void testWatchLocalOnlyHotspot() throws Exception { TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); |