diff options
344 files changed, 8130 insertions, 4015 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index f8b2f32e1a2f..ac58f3d6a94d 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -1224,7 +1224,7 @@ public class DeviceIdleController extends SystemService IDLE_FACTOR = mParser.getFloat(KEY_IDLE_FACTOR, 2f); MIN_TIME_TO_ALARM = mParser.getDurationMillis(KEY_MIN_TIME_TO_ALARM, - !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L); + !COMPRESS_TIME ? 30 * 60 * 1000L : 6 * 60 * 1000L); MAX_TEMP_APP_WHITELIST_DURATION = mParser.getDurationMillis( KEY_MAX_TEMP_APP_WHITELIST_DURATION, 5 * 60 * 1000L); MMS_TEMP_APP_WHITELIST_DURATION = mParser.getDurationMillis( diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 07a99084e9d5..2aa2275cc67b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2182,17 +2182,18 @@ public class JobSchedulerService extends com.android.server.SystemService } final boolean jobExists = mJobs.containsJob(job); - final boolean userStarted = areUsersStartedLocked(job); + final boolean backingUp = mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0; if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() - + " exists=" + jobExists + " userStarted=" + userStarted); + + " exists=" + jobExists + " userStarted=" + userStarted + + " backingUp=" + backingUp); } // These are also fairly cheap to check, though they typically will not // be conditions we fail. - if (!jobExists || !userStarted) { + if (!jobExists || !userStarted || backingUp) { return false; } @@ -2265,15 +2266,17 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean jobExists = mJobs.containsJob(job); final boolean userStarted = areUsersStartedLocked(job); + final boolean backingUp = mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0; if (DEBUG) { Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString() - + " exists=" + jobExists + " userStarted=" + userStarted); + + " exists=" + jobExists + " userStarted=" + userStarted + + " backingUp=" + backingUp); } // These are also fairly cheap to check, though they typically will not // be conditions we fail. - if (!jobExists || !userStarted) { + if (!jobExists || !userStarted || backingUp) { return false; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java index f74eb3b9a45f..e2c8f649fdb7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java @@ -19,10 +19,12 @@ package com.android.server.job.controllers.idle; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AlarmManager; +import android.app.UiModeManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.PowerManager; import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -39,6 +41,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id || Log.isLoggable(TAG, Log.DEBUG); private AlarmManager mAlarm; + private PowerManager mPowerManager; // After construction, mutations of idle/screen-on state will only happen // on the main looper thread, either in onReceive() or in an alarm callback. @@ -47,6 +50,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id private boolean mIdle; private boolean mScreenOn; private boolean mDockIdle; + private boolean mInCarMode; private IdlenessListener mIdleListener; private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { @@ -59,6 +63,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id mIdle = false; mScreenOn = true; mDockIdle = false; + mInCarMode = false; } @Override @@ -74,6 +79,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id mIdleWindowSlop = context.getResources().getInteger( com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop); mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + mPowerManager = context.getSystemService(PowerManager.class); IntentFilter filter = new IntentFilter(); @@ -92,6 +98,10 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id filter.addAction(Intent.ACTION_DOCK_IDLE); filter.addAction(Intent.ACTION_DOCK_ACTIVE); + // Car mode + filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED); + filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED); + context.registerReceiver(this, filter); } @@ -100,6 +110,8 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id pw.print(" mIdle: "); pw.println(mIdle); pw.print(" mScreenOn: "); pw.println(mScreenOn); pw.print(" mDockIdle: "); pw.println(mDockIdle); + pw.print(" mInCarMode: "); + pw.println(mInCarMode); } @Override @@ -116,6 +128,9 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id proto.write( StateControllerProto.IdleController.IdlenessTracker.DeviceIdlenessTracker.IS_DOCK_IDLE, mDockIdle); + proto.write( + StateControllerProto.IdleController.IdlenessTracker.DeviceIdlenessTracker.IN_CAR_MODE, + mInCarMode); proto.end(diToken); proto.end(token); @@ -124,63 +139,90 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (action.equals(Intent.ACTION_SCREEN_ON) - || action.equals(Intent.ACTION_DREAMING_STOPPED) - || action.equals(Intent.ACTION_DOCK_ACTIVE)) { - if (action.equals(Intent.ACTION_DOCK_ACTIVE)) { + if (DEBUG) { + Slog.v(TAG, "Received action: " + action); + } + switch (action) { + case Intent.ACTION_DOCK_ACTIVE: if (!mScreenOn) { // Ignore this intent during screen off return; - } else { - mDockIdle = false; } - } else { + // Intentional fallthrough + case Intent.ACTION_DREAMING_STOPPED: + if (!mPowerManager.isInteractive()) { + // Ignore this intent if the device isn't interactive. + return; + } + // Intentional fallthrough + case Intent.ACTION_SCREEN_ON: mScreenOn = true; mDockIdle = false; - } - if (DEBUG) { - Slog.v(TAG,"exiting idle : " + action); - } - //cancel the alarm - mAlarm.cancel(mIdleAlarmListener); - if (mIdle) { - // possible transition to not-idle - mIdle = false; - mIdleListener.reportNewIdleState(mIdle); - } - } else if (action.equals(Intent.ACTION_SCREEN_OFF) - || action.equals(Intent.ACTION_DREAMING_STARTED) - || action.equals(Intent.ACTION_DOCK_IDLE)) { - // when the screen goes off or dreaming starts or wireless charging dock in idle, - // we schedule the alarm that will tell us when we have decided the device is - // truly idle. - if (action.equals(Intent.ACTION_DOCK_IDLE)) { - if (!mScreenOn) { - // Ignore this intent during screen off - return; + if (DEBUG) { + Slog.v(TAG, "exiting idle"); + } + cancelIdlenessCheck(); + if (mIdle) { + mIdle = false; + mIdleListener.reportNewIdleState(mIdle); + } + break; + case Intent.ACTION_SCREEN_OFF: + case Intent.ACTION_DREAMING_STARTED: + case Intent.ACTION_DOCK_IDLE: + // when the screen goes off or dreaming starts or wireless charging dock in idle, + // we schedule the alarm that will tell us when we have decided the device is + // truly idle. + if (action.equals(Intent.ACTION_DOCK_IDLE)) { + if (!mScreenOn) { + // Ignore this intent during screen off + return; + } else { + mDockIdle = true; + } } else { - mDockIdle = true; + mScreenOn = false; + mDockIdle = false; } - } else { - mScreenOn = false; - mDockIdle = false; - } + maybeScheduleIdlenessCheck(action); + break; + case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED: + mInCarMode = true; + cancelIdlenessCheck(); + if (mIdle) { + mIdle = false; + mIdleListener.reportNewIdleState(mIdle); + } + break; + case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED: + mInCarMode = false; + maybeScheduleIdlenessCheck(action); + break; + case ActivityManagerService.ACTION_TRIGGER_IDLE: + handleIdleTrigger(); + break; + } + } + + private void maybeScheduleIdlenessCheck(String reason) { + if ((!mScreenOn || mDockIdle) && !mInCarMode) { final long nowElapsed = sElapsedRealtimeClock.millis(); final long when = nowElapsed + mInactivityIdleThreshold; if (DEBUG) { - Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" - + when); + Slog.v(TAG, "Scheduling idle : " + reason + " now:" + nowElapsed + " when=" + when); } mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); - } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { - handleIdleTrigger(); } } + private void cancelIdlenessCheck() { + mAlarm.cancel(mIdleAlarmListener); + } + private void handleIdleTrigger() { // idle time starts now. Do not set mIdle if screen is on. - if (!mIdle && (!mScreenOn || mDockIdle)) { + if (!mIdle && (!mScreenOn || mDockIdle) && !mInCarMode) { if (DEBUG) { Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); } @@ -189,7 +231,7 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id } else { if (DEBUG) { Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" - + mIdle + " screen=" + mScreenOn); + + mIdle + " screen=" + mScreenOn + " car=" + mInCarMode); } } } diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index e533b7a7d6f3..073fddf8f615 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -51,6 +51,7 @@ import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.PsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.wav.WavExtractor; +import com.google.android.exoplayer2.upstream.DataReader; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; @@ -60,7 +61,6 @@ import com.google.android.exoplayer2.video.ColorInfo; import java.io.EOFException; import java.io.IOException; -import java.io.InterruptedIOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -848,9 +848,9 @@ public final class MediaParser { private final String[] mParserNamesPool; private final PositionHolder mPositionHolder; private final InputReadingDataSource mDataSource; - private final ExtractorInputAdapter mScratchExtractorInputAdapter; + private final DataReaderAdapter mScratchDataReaderAdapter; private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter; - private String mExtractorName; + private String mParserName; private Extractor mExtractor; private ExtractorInput mExtractorInput; private long mPendingSeekPosition; @@ -924,7 +924,7 @@ public final class MediaParser { @NonNull @ParserName public String getParserName() { - return mExtractorName; + return mParserName; } /** @@ -958,25 +958,21 @@ public final class MediaParser { // TODO: Apply parameters when creating extractor instances. if (mExtractor == null) { - if (!mExtractorName.equals(PARSER_NAME_UNKNOWN)) { - mExtractor = EXTRACTOR_FACTORIES_BY_NAME.get(mExtractorName).createInstance(); + if (!mParserName.equals(PARSER_NAME_UNKNOWN)) { + mExtractor = createExtractor(mParserName); mExtractor.init(new ExtractorOutputAdapter()); } else { for (String parserName : mParserNamesPool) { Extractor extractor = createExtractor(parserName); try { if (extractor.sniff(mExtractorInput)) { - mExtractorName = parserName; + mParserName = parserName; mExtractor = extractor; mExtractor.init(new ExtractorOutputAdapter()); break; } } catch (EOFException e) { // Do nothing. - } catch (InterruptedException e) { - // TODO: Remove this exception replacement once we update the ExoPlayer - // version. - throw new InterruptedIOException(); } finally { mExtractorInput.resetPeekPosition(); } @@ -999,9 +995,6 @@ public final class MediaParser { result = mExtractor.read(mExtractorInput, mPositionHolder); } catch (ParserException e) { throw new ParsingException(e); - } catch (InterruptedException e) { - // TODO: Remove this exception replacement once we update the ExoPlayer version. - throw new InterruptedIOException(); } if (result == Extractor.RESULT_END_OF_INPUT) { return false; @@ -1051,11 +1044,11 @@ public final class MediaParser { mParserParameters = new HashMap<>(); mOutputConsumer = outputConsumer; mParserNamesPool = parserNamesPool; - mExtractorName = sniff ? PARSER_NAME_UNKNOWN : parserNamesPool[0]; + mParserName = sniff ? PARSER_NAME_UNKNOWN : parserNamesPool[0]; mPositionHolder = new PositionHolder(); mDataSource = new InputReadingDataSource(); removePendingSeek(); - mScratchExtractorInputAdapter = new ExtractorInputAdapter(); + mScratchDataReaderAdapter = new DataReaderAdapter(); mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter(); } @@ -1097,7 +1090,7 @@ public final class MediaParser { getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) ? Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS : 0; - return new Mp4Extractor(); + return new Mp4Extractor(flags); case PARSER_NAME_MP3: flags |= getBooleanParameter(PARAMETER_MP3_DISABLE_ID3) @@ -1270,12 +1263,12 @@ public final class MediaParser { } @Override - public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + public int sampleData(DataReader input, int length, boolean allowEndOfInput) throws IOException { - mScratchExtractorInputAdapter.setExtractorInput(input, length); - long positionBeforeReading = mScratchExtractorInputAdapter.getPosition(); - mOutputConsumer.onSampleDataFound(mTrackIndex, mScratchExtractorInputAdapter); - return (int) (mScratchExtractorInputAdapter.getPosition() - positionBeforeReading); + mScratchDataReaderAdapter.setDataReader(input, length); + long positionBeforeReading = mScratchDataReaderAdapter.getPosition(); + mOutputConsumer.onSampleDataFound(mTrackIndex, mScratchDataReaderAdapter); + return (int) (mScratchDataReaderAdapter.getPosition() - positionBeforeReading); } @Override @@ -1297,14 +1290,14 @@ public final class MediaParser { } } - private static final class ExtractorInputAdapter implements InputReader { + private static final class DataReaderAdapter implements InputReader { - private ExtractorInput mExtractorInput; + private DataReader mDataReader; private int mCurrentPosition; private long mLength; - public void setExtractorInput(ExtractorInput extractorInput, long length) { - mExtractorInput = extractorInput; + public void setDataReader(DataReader dataReader, long length) { + mDataReader = dataReader; mCurrentPosition = 0; mLength = length; } @@ -1314,12 +1307,7 @@ public final class MediaParser { @Override public int read(byte[] buffer, int offset, int readLength) throws IOException { int readBytes = 0; - try { - readBytes = mExtractorInput.read(buffer, offset, readLength); - } catch (InterruptedException e) { - // TODO: Remove this exception replacement once we update the ExoPlayer version. - throw new InterruptedIOException(); - } + readBytes = mDataReader.read(buffer, offset, readLength); mCurrentPosition += readBytes; return readBytes; } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index b3579045b6a5..b4519b769b7c 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -104,6 +104,7 @@ cc_defaults { "src/subscriber/IncidentdReporter.cpp", "src/subscriber/SubscriberReporter.cpp", "src/uid_data.proto", + "src/utils/NamedLatch.cpp", ], local_include_dirs: [ @@ -290,7 +291,14 @@ cc_binary { cc_test { name: "statsd_test", defaults: ["statsd_defaults"], - test_suites: ["device-tests"], + test_suites: ["device-tests", "mts"], + + //TODO(b/153588990): Remove when the build system properly separates + //32bit and 64bit architectures. + multilib: { + lib32: { suffix: "32", }, + lib64: { suffix: "64", }, + }, cflags: [ "-Wall", @@ -361,6 +369,7 @@ cc_test { "tests/StatsService_test.cpp", "tests/storage/StorageManager_test.cpp", "tests/UidMap_test.cpp", + "tests/utils/NamedLatch_test.cpp", ], static_libs: [ diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 92e09ea0f8f9..e251399776fb 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -181,6 +181,7 @@ public: return false; } + bool matches(const Matcher& that) const; }; @@ -360,7 +361,9 @@ struct Value { class Annotations { public: - Annotations() {} + Annotations() { + setNested(true); // Nested = true by default + } // This enum stores where particular annotations can be found in the // bitmask. Note that these pos do not correspond to annotation ids. @@ -379,7 +382,9 @@ public: inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } - inline void setResetState(int resetState) { mResetState = resetState; } + inline void setResetState(int32_t resetState) { + mResetState = resetState; + } // Default value = false inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } @@ -395,7 +400,9 @@ public: // If a reset state is not sent in the StatsEvent, returns -1. Note that a // reset satate is only sent if and only if a reset should be triggered. - inline int getResetState() const { return mResetState; } + inline int32_t getResetState() const { + return mResetState; + } private: inline void setBitmaskAtPos(int pos, bool value) { @@ -411,7 +418,7 @@ private: // there are only 4 booleans, just one byte is required. uint8_t mBooleanBitmask = 0; - int mResetState = -1; + int32_t mResetState = -1; }; /** diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 29249f4a6c55..eba66e0cb7b0 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -180,6 +180,23 @@ bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue> return num_matches > 0; } +bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output) { + size_t num_matches = 0; + const int32_t simpleFieldMask = 0xff7f0000; + const int32_t attributionUidFieldMask = 0xff7f7f7f; + for (const auto& value : values) { + if (value.mAnnotations.isPrimaryField()) { + output->addValue(value); + output->mutableValue(num_matches)->mField.setTag(value.mField.getTag()); + const int32_t mask = + isAttributionUidField(value) ? attributionUidFieldMask : simpleFieldMask; + output->mutableValue(num_matches)->mField.setField(value.mField.getField() & mask); + num_matches++; + } + } + return num_matches > 0; +} + void filterGaugeValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values, std::vector<FieldValue>* output) { for (const auto& field : matcherFields) { diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 33a502497746..bd011005a301 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -154,6 +154,18 @@ bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<F HashableDimensionKey* output); /** + * Creating HashableDimensionKeys from State Primary Keys in FieldValues. + * + * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL + * in it. This is because: for example, when we create dimension from last uid in attribution chain, + * In one event, uid 1000 is at position 5 and it's the last + * In another event, uid 1000 is at position 6, and it's the last + * these 2 events should be mapped to the same dimension. So we will remove the original position + * from the dimension key for the uid field (by applying 0x80 bit mask). + */ +bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output); + +/** * Filter the values from FieldValues using the matchers. * * In contrast to the above function, this function will not do any modification to the original diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index dd1d40083a6b..ae7a8d0d30cc 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -118,8 +118,9 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ } })), mEventQueue(queue), - mStatsCompanionServiceDeathRecipient(AIBinder_DeathRecipient_new( - StatsService::statsCompanionServiceDied)) { + mBootCompleteLatch({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}), + mStatsCompanionServiceDeathRecipient( + AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) { mUidMap = UidMap::getInstance(); mPullerManager = new StatsPullerManager(); StatsPuller::SetUidMap(mUidMap); @@ -164,6 +165,12 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ std::thread pushedEventThread([this] { readLogs(); }); pushedEventThread.detach(); } + + std::thread bootCompletedThread([this] { + mBootCompleteLatch.wait(); + VLOG("In the boot completed thread"); + }); + bootCompletedThread.detach(); } StatsService::~StatsService() { @@ -939,6 +946,7 @@ Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) { packageNames, installers); + mBootCompleteLatch.countDown(kUidMapReceivedTag); VLOG("StatsService::informAllUidData UidData proto parsed successfully."); return Status::ok(); } @@ -1058,7 +1066,7 @@ Status StatsService::bootCompleted() { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::bootCompleted was called"); - + mBootCompleteLatch.countDown(kBootCompleteTag); return Status::ok(); } @@ -1227,7 +1235,7 @@ Status StatsService::allPullersFromBootRegistered() { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::allPullersFromBootRegistered was called"); - + mBootCompleteLatch.countDown(kAllPullersRegisteredTag); return Status::ok(); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 23d4c1bd199d..79324d89d8e8 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -17,7 +17,14 @@ #ifndef STATS_SERVICE_H #define STATS_SERVICE_H +#include <aidl/android/os/BnStatsd.h> +#include <aidl/android/os/IPendingIntentRef.h> +#include <aidl/android/os/IPullAtomCallback.h> #include <gtest/gtest_prod.h> +#include <utils/Looper.h> + +#include <mutex> + #include "StatsLogProcessor.h" #include "anomaly/AlarmMonitor.h" #include "config/ConfigManager.h" @@ -26,13 +33,7 @@ #include "packages/UidMap.h" #include "shell/ShellSubscriber.h" #include "statscompanion_util.h" - -#include <aidl/android/os/BnStatsd.h> -#include <aidl/android/os/IPendingIntentRef.h> -#include <aidl/android/os/IPullAtomCallback.h> -#include <utils/Looper.h> - -#include <mutex> +#include "utils/NamedLatch.h" using namespace android; using namespace android::os; @@ -385,6 +386,11 @@ private: mutable mutex mShellSubscriberMutex; std::shared_ptr<LogEventQueue> mEventQueue; + NamedLatch mBootCompleteLatch; + static const inline string kBootCompleteTag = "BOOT_COMPLETE"; + static const inline string kUidMapReceivedTag = "UID_MAP"; + static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED"; + ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient; FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1f090fda2ce7..b7ed6eb16013 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -421,6 +421,8 @@ message Atom { TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"]; LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"]; PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"]; + UserLifecycleJourneyReported user_lifecycle_journey_reported = 264 [(module) = "framework"]; + UserLifecycleEventOccurred user_lifecycle_event_occurred = 265 [(module) = "framework"]; SdkExtensionStatus sdk_extension_status = 354; } @@ -9357,3 +9359,82 @@ message SettingSnapshot { // Android user index. 0 for primary user, 10, 11 for secondary or profile user optional int32 user_id = 7; } + +/** + * An event logged to indicate that a user journey is about to be performed. This atom includes + * relevant information about the users involved in the journey. A UserLifecycleEventOccurred event + * will immediately follow this atom which will describe the event(s) and its state. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/UserController.java + * frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java + */ +message UserLifecycleJourneyReported { + // An identifier to track a chain of user lifecycle events occurring (referenced in the + // UserLifecycleEventOccurred atom) + optional int64 session_id = 1; + + // Indicates what type of user journey this session is related to + enum Journey { + UNKNOWN = 0; // Undefined user lifecycle journey + USER_SWITCH_UI = 1; // A user switch journey where a UI is shown + USER_SWITCH_FG = 2; // A user switch journey without a UI shown + USER_START = 3; // A user start journey + USER_CREATE = 4; // A user creation journey + } + optional Journey journey = 2; + // Which user the journey is originating from - could be -1 for certain phases (eg USER_CREATE) + // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest) + optional int32 origin_user = 3; + // Which user the journey is targeting + // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest) + optional int32 target_user = 4; + + // What is the user type of the target user + // These should be in sync with USER_TYPE_* flags defined in UserManager.java + enum UserType { + TYPE_UNKNOWN = 0; + FULL_SYSTEM = 1; + FULL_SECONDARY = 2; + FULL_GUEST = 3; + FULL_DEMO = 4; + FULL_RESTRICTED = 5; + PROFILE_MANAGED = 6; + SYSTEM_HEADLESS = 7; + } + optional UserType user_type = 5; + // What are the flags attached to the target user + optional int32 user_flags = 6; +} + +/** + * An event logged when a specific user lifecycle event is performed. These events should be + * correlated with a UserLifecycleJourneyReported atom via the session_id. + * Note: journeys can span over multiple events, hence some events may share a single session id. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/UserController.java + * frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java + */ +message UserLifecycleEventOccurred { + // An id which links back to user details (reported in the UserLifecycleJourneyReported atom) + optional int64 session_id = 1; + // The target user for this event (same as target_user in the UserLifecycleJourneyReported atom) + // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest) + optional int32 user_id = 2; + + enum Event { + UNKNOWN = 0; // Indicates that the associated user journey timed-out or resulted in an error + SWITCH_USER = 1; // Indicates that this is a user switch event + START_USER = 2; // Indicates that this is a user start event + CREATE_USER = 3; // Indicates that this is a user create event + } + optional Event event = 3; + + enum State { + NONE = 0; // Indicates the associated event has no start/end defined + BEGIN = 1; + FINISH = 2; + } + optional State state = 4; // Represents the state of an event (beginning/ending) +} diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 8b6a86464155..61cd01728ab1 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -293,7 +293,8 @@ void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) { } const bool exclusiveState = readNextValue<uint8_t>(); - mValues[mValues.size() - 1].mAnnotations.setExclusiveState(exclusiveState); + mExclusiveStateFieldIndex = mValues.size() - 1; + mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState); } void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 4eeb7d64a463..41fdcc2cbe7a 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -145,7 +145,7 @@ public: } // Default value = false - inline bool shouldTruncateTimestamp() { + inline bool shouldTruncateTimestamp() const { return mTruncateTimestamp; } @@ -170,6 +170,20 @@ public: return mAttributionChainIndex; } + // Returns the index of the exclusive state field within the FieldValues vector if + // an exclusive state exists. If there is no exclusive state field, returns -1. + // + // If the index within the atom definition is desired, do the following: + // int vectorIndex = LogEvent.getExclusiveStateFieldIndex(); + // if (vectorIndex != -1) { + // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // int atomIndex = v.mField.getPosAtDepth(0); + // } + // Note that atomIndex is 1-indexed. + inline int getExclusiveStateFieldIndex() const { + return mExclusiveStateFieldIndex; + } + inline LogEvent makeCopy() { return LogEvent(*this); } @@ -297,6 +311,7 @@ private: bool mTruncateTimestamp = false; int mUidFieldIndex = -1; int mAttributionChainIndex = -1; + int mExclusiveStateFieldIndex = -1; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index d79b6a21c19f..e3945334aeca 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -38,20 +38,27 @@ using std::make_shared; shared_ptr<StatsService> gStatsService = nullptr; -void sigHandler(int sig) { - if (gStatsService != nullptr) { - gStatsService->Terminate(); +void signalHandler(int sig) { + if (sig == SIGPIPE) { + // ShellSubscriber uses SIGPIPE as a signal to detect the end of the + // client process. Don't prematurely exit(1) here. Instead, ignore the + // signal and allow the write call to return EPIPE. + ALOGI("statsd received SIGPIPE. Ignoring signal."); + return; } + + if (gStatsService != nullptr) gStatsService->Terminate(); ALOGW("statsd terminated on receiving signal %d.", sig); exit(1); } -void registerSigHandler() +void registerSignalHandlers() { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; - sa.sa_handler = sigHandler; + sa.sa_handler = signalHandler; + sigaction(SIGPIPE, &sa, nullptr); sigaction(SIGHUP, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); sigaction(SIGQUIT, &sa, nullptr); @@ -79,7 +86,7 @@ int main(int /*argc*/, char** /*argv*/) { return -1; } - registerSigHandler(); + registerSignalHandlers(); gStatsService->sayHiToStatsCompanion(); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 6833f8dd0114..d68f64ae40b0 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -151,8 +151,7 @@ void EventMetricProducer::onMatchedLogEventInternalLocked( uint64_t wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - const int64_t elapsedTimeNs = truncateTimestampIfNecessary( - event.GetTagId(), event.GetElapsedTimestampNs()); + const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_ELAPSED_TIMESTAMP_NANOS, (long long) elapsedTimeNs); uint64_t eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS); diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 42bbd8eb4d35..c4bd0549465a 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -270,11 +270,9 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->end(atomsToken); } for (const auto& atom : bucket.mGaugeAtoms) { - const int64_t elapsedTimestampNs = - truncateTimestampIfNecessary(mAtomId, atom.mElapsedTimestamps); - protoOutput->write( - FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_ELAPSED_ATOM_TIMESTAMP, - (long long)elapsedTimestampNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | + FIELD_ID_ELAPSED_ATOM_TIMESTAMP, + (long long)atom.mElapsedTimestampNs); } } protoOutput->end(bucketInfoToken); @@ -477,7 +475,9 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( if ((*mCurrentSlicedBucket)[eventKey].size() >= mGaugeAtomsPerDimensionLimit) { return; } - GaugeAtom gaugeAtom(getGaugeFields(event), eventTimeNs); + + const int64_t truncatedElapsedTimestampNs = truncateTimestampIfNecessary(event); + GaugeAtom gaugeAtom(getGaugeFields(event), truncatedElapsedTimestampNs); (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom); // Anomaly detection on gauge metric only works when there is one numeric // field specified. diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 79ec71120f18..aa0cae26080d 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -35,10 +35,10 @@ namespace statsd { struct GaugeAtom { GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs) - : mFields(fields), mElapsedTimestamps(elapsedTimeNs) { + : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) { } std::shared_ptr<vector<FieldValue>> mFields; - int64_t mElapsedTimestamps; + int64_t mElapsedTimestampNs; }; struct GaugeBucket { diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 4550e65b6438..6aba13ca7859 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -451,7 +451,7 @@ protected: std::vector<int32_t> mSlicedStateAtoms; // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>). - std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap; + const std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap; // MetricStateLinks defined in statsd_config that link fields in the state // atom to fields in the "what" atom. diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 2fcb13b709f9..88616dde61b7 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -795,9 +795,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t for (const auto& it : allMetricProducers) { // Register metrics to StateTrackers for (int atomId : it->getSlicedStateAtoms()) { - if (!StateManager::getInstance().registerListener(atomId, it)) { - return false; - } + StateManager::getInstance().registerListener(atomId, it); } } return true; diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp index ea776fae0583..5514b446b306 100644 --- a/cmds/statsd/src/state/StateManager.cpp +++ b/cmds/statsd/src/state/StateManager.cpp @@ -38,20 +38,12 @@ void StateManager::onLogEvent(const LogEvent& event) { } } -bool StateManager::registerListener(const int32_t atomId, wp<StateListener> listener) { +void StateManager::registerListener(const int32_t atomId, wp<StateListener> listener) { // Check if state tracker already exists. if (mStateTrackers.find(atomId) == mStateTrackers.end()) { - // Create a new state tracker iff atom is a state atom. - auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(atomId); - if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) { - mStateTrackers[atomId] = new StateTracker(atomId, it->second); - } else { - ALOGE("StateManager cannot register listener, Atom %d is not a state atom", atomId); - return false; - } + mStateTrackers[atomId] = new StateTracker(atomId); } mStateTrackers[atomId]->registerListener(listener); - return true; } void StateManager::unregisterListener(const int32_t atomId, wp<StateListener> listener) { diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h index 8b3a4218238c..577a0f51e38b 100644 --- a/cmds/statsd/src/state/StateManager.h +++ b/cmds/statsd/src/state/StateManager.h @@ -45,10 +45,11 @@ public: // Notifies the correct StateTracker of an event. void onLogEvent(const LogEvent& event); - // Returns true if atomId is being tracked and is associated with a state - // atom. StateManager notifies the correct StateTracker to register listener. + // Notifies the StateTracker for the given atomId to register listener. // If the correct StateTracker does not exist, a new StateTracker is created. - bool registerListener(const int32_t atomId, wp<StateListener> listener); + // Note: StateTrackers can be created for non-state atoms. They are essentially empty and + // do not perform any actions. + void registerListener(const int32_t atomId, wp<StateListener> listener); // Notifies the correct StateTracker to unregister a listener // and removes the tracker if it no longer has any listeners. diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index ab861275073c..b7f314a819df 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -25,81 +25,43 @@ namespace android { namespace os { namespace statsd { -StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo) - : mAtomId(atomId), - mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)), - mNested(stateAtomInfo.nested) { - // create matcher for each primary field - for (const auto& primaryField : stateAtomInfo.primaryFields) { - if (primaryField == util::FIRST_UID_IN_CHAIN) { - Matcher matcher = getFirstUidMatcher(atomId); - mPrimaryFields.push_back(matcher); - } else { - Matcher matcher = getSimpleMatcher(atomId, primaryField); - mPrimaryFields.push_back(matcher); - } - } - - if (stateAtomInfo.defaultState != util::UNSET_VALUE) { - mDefaultState = stateAtomInfo.defaultState; - } - - if (stateAtomInfo.resetState != util::UNSET_VALUE) { - mResetState = stateAtomInfo.resetState; - } +StateTracker::StateTracker(const int32_t atomId) : mField(atomId, 0) { } void StateTracker::onLogEvent(const LogEvent& event) { - int64_t eventTimeNs = event.GetElapsedTimestampNs(); + const int64_t eventTimeNs = event.GetElapsedTimestampNs(); // Parse event for primary field values i.e. primary key. HashableDimensionKey primaryKey; - if (mPrimaryFields.size() > 0) { - if (!filterValues(mPrimaryFields, event.getValues(), &primaryKey) || - primaryKey.getValues().size() != mPrimaryFields.size()) { - ALOGE("StateTracker error extracting primary key from log event."); - handleReset(eventTimeNs); - return; - } - } else { - // Use an empty HashableDimensionKey if atom has no primary fields. - primaryKey = DEFAULT_DIMENSION_KEY; - } + filterPrimaryKey(event.getValues(), &primaryKey); - // Parse event for state value. FieldValue stateValue; - if (!filterValues(mStateField, event.getValues(), &stateValue) || - stateValue.mValue.getType() != INT) { + if (!getStateFieldValueFromLogEvent(event, &stateValue)) { + ALOGE("StateTracker error extracting state from log event. Missing exclusive state field."); + clearStateForPrimaryKey(eventTimeNs, primaryKey); + return; + } + + mField.setField(stateValue.mField.getField()); + + if (stateValue.mValue.getType() != INT) { ALOGE("StateTracker error extracting state from log event. Type: %d", stateValue.mValue.getType()); - handlePartialReset(eventTimeNs, primaryKey); + clearStateForPrimaryKey(eventTimeNs, primaryKey); return; } - int32_t state = stateValue.mValue.int_value; - if (state == mResetState) { - VLOG("StateTracker Reset state: %s", stateValue.mValue.toString().c_str()); - handleReset(eventTimeNs); + const int32_t resetState = stateValue.mAnnotations.getResetState(); + if (resetState != -1) { + VLOG("StateTracker new reset state: %d", resetState); + handleReset(eventTimeNs, resetState); return; } - // Track and update state. - int32_t oldState = 0; - int32_t newState = 0; - updateState(primaryKey, state, &oldState, &newState); - - // Notify all listeners if state has changed. - if (oldState != newState) { - VLOG("StateTracker updated state"); - for (auto listener : mListeners) { - auto sListener = listener.promote(); // safe access to wp<> - if (sListener != nullptr) { - sListener->onStateChanged(eventTimeNs, mAtomId, primaryKey, oldState, newState); - } - } - } else { - VLOG("StateTracker NO updated state"); - } + const int32_t newState = stateValue.mValue.int_value; + const bool nested = stateValue.mAnnotations.isNested(); + StateValueInfo* stateValueInfo = &mStateMap[primaryKey]; + updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo); } void StateTracker::registerListener(wp<StateListener> listener) { @@ -111,81 +73,62 @@ void StateTracker::unregisterListener(wp<StateListener> listener) { } bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const { - output->mField = mStateField.mMatcher; - - // Check that the query key has the correct number of primary fields. - if (queryKey.getValues().size() == mPrimaryFields.size()) { - auto it = mStateMap.find(queryKey); - if (it != mStateMap.end()) { - output->mValue = it->second.state; - return true; - } - } else if (queryKey.getValues().size() > mPrimaryFields.size()) { - ALOGE("StateTracker query key size %zu > primary key size %zu is illegal", - queryKey.getValues().size(), mPrimaryFields.size()); - } else { - ALOGE("StateTracker query key size %zu < primary key size %zu is not supported", - queryKey.getValues().size(), mPrimaryFields.size()); + output->mField = mField; + + if (const auto it = mStateMap.find(queryKey); it != mStateMap.end()) { + output->mValue = it->second.state; + return true; } - // Set the state value to default state if: - // - query key size is incorrect - // - query key is not found in state map - output->mValue = mDefaultState; + // Set the state value to kStateUnknown if query key is not found in state map. + output->mValue = kStateUnknown; return false; } -void StateTracker::handleReset(const int64_t eventTimeNs) { +void StateTracker::handleReset(const int64_t eventTimeNs, const int32_t newState) { VLOG("StateTracker handle reset"); - for (const auto pair : mStateMap) { - for (auto l : mListeners) { - auto sl = l.promote(); - if (sl != nullptr) { - sl->onStateChanged(eventTimeNs, mAtomId, pair.first, pair.second.state, - mDefaultState); - } - } + for (auto& [primaryKey, stateValueInfo] : mStateMap) { + updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, + false /* nested; treat this state change as not nested */, + &stateValueInfo); } - mStateMap.clear(); } -void StateTracker::handlePartialReset(const int64_t eventTimeNs, - const HashableDimensionKey& primaryKey) { - VLOG("StateTracker handle partial reset"); - if (mStateMap.find(primaryKey) != mStateMap.end()) { - for (auto l : mListeners) { - auto sl = l.promote(); - if (sl != nullptr) { - sl->onStateChanged(eventTimeNs, mAtomId, primaryKey, - mStateMap.find(primaryKey)->second.state, mDefaultState); - } - } - mStateMap.erase(primaryKey); +void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey) { + VLOG("StateTracker clear state for primary key"); + const std::unordered_map<HashableDimensionKey, StateValueInfo>::iterator it = + mStateMap.find(primaryKey); + + // If there is no entry for the primaryKey in mStateMap, then the state is already + // kStateUnknown. + if (it != mStateMap.end()) { + updateStateForPrimaryKey(eventTimeNs, primaryKey, kStateUnknown, + false /* nested; treat this state change as not nested */, + &it->second); } } -void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int32_t eventState, - int32_t* oldState, int32_t* newState) { - // get old state (either current state in map or default state) - auto it = mStateMap.find(primaryKey); - if (it != mStateMap.end()) { - *oldState = it->second.state; - } else { - *oldState = mDefaultState; +void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey, + const int32_t newState, const bool nested, + StateValueInfo* stateValueInfo) { + const int32_t oldState = stateValueInfo->state; + + if (kStateUnknown == newState) { + mStateMap.erase(primaryKey); } // Update state map for non-nested counting case. // Every state event triggers a state overwrite. - if (!mNested) { - if (eventState == mDefaultState) { - // remove (key, state) pair if state returns to default state - VLOG("\t StateTracker changed to default state") - mStateMap.erase(primaryKey); - } else { - mStateMap[primaryKey].state = eventState; - mStateMap[primaryKey].count = 1; + if (!nested) { + stateValueInfo->state = newState; + stateValueInfo->count = 1; + + // Notify listeners if state has changed. + if (oldState != newState) { + notifyListeners(eventTimeNs, primaryKey, oldState, newState); } - *newState = eventState; return; } @@ -197,31 +140,47 @@ void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int // number of OFF events as ON events. // // In atoms.proto, a state atom with nested counting enabled - // must only have 2 states and one of the states must be the default state. - it = mStateMap.find(primaryKey); - if (it != mStateMap.end()) { - *newState = it->second.state; - if (eventState == it->second.state) { - it->second.count++; - } else if (eventState == mDefaultState) { - if ((--it->second.count) == 0) { - mStateMap.erase(primaryKey); - *newState = mDefaultState; - } - } else { - ALOGE("StateTracker Nest counting state has a third state instead of the binary state " - "limit."); - return; + // must only have 2 states. There is no enforcemnt here of this requirement. + // The atom must be logged correctly. + if (kStateUnknown == newState) { + if (kStateUnknown != oldState) { + notifyListeners(eventTimeNs, primaryKey, oldState, newState); } - } else { - if (eventState != mDefaultState) { - mStateMap[primaryKey].state = eventState; - mStateMap[primaryKey].count = 1; + } else if (oldState == kStateUnknown) { + stateValueInfo->state = newState; + stateValueInfo->count = 1; + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } else if (oldState == newState) { + stateValueInfo->count++; + } else if (--stateValueInfo->count == 0) { + stateValueInfo->state = newState; + stateValueInfo->count = 1; + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } +} + +void StateTracker::notifyListeners(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState) { + for (auto l : mListeners) { + auto sl = l.promote(); + if (sl != nullptr) { + sl->onStateChanged(eventTimeNs, mField.getTag(), primaryKey, oldState, newState); } - *newState = eventState; } } +bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) { + const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex(); + if (-1 == exclusiveStateFieldIndex) { + ALOGE("error extracting state from log event. Missing exclusive state field."); + return false; + } + + *output = event.getValues()[exclusiveStateFieldIndex]; + return true; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h index 154750e6da3d..c5f6315fc992 100644 --- a/cmds/statsd/src/state/StateTracker.h +++ b/cmds/statsd/src/state/StateTracker.h @@ -15,7 +15,6 @@ */ #pragma once -#include <atoms_info.h> #include <utils/RefBase.h> #include "HashableDimensionKey.h" #include "logd/LogEvent.h" @@ -30,7 +29,7 @@ namespace statsd { class StateTracker : public virtual RefBase { public: - StateTracker(const int32_t atomId, const android::util::StateAtomFieldOptions& stateAtomInfo); + StateTracker(const int32_t atomId); virtual ~StateTracker(){}; @@ -60,21 +59,11 @@ public: private: struct StateValueInfo { - int32_t state; // state value - int count; // nested count (only used for binary states) + int32_t state = kStateUnknown; // state value + int count = 0; // nested count (only used for binary states) }; - const int32_t mAtomId; // id of the state atom being tracked - - Matcher mStateField; // matches the atom's exclusive state field - - std::vector<Matcher> mPrimaryFields; // matches the atom's primary fields - - int32_t mDefaultState = kStateUnknown; - - int32_t mResetState = kStateUnknown; - - const bool mNested; + Field mField; // Maps primary key to state value info std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; @@ -82,20 +71,24 @@ private: // Set of all StateListeners (objects listening for state changes) std::set<wp<StateListener>> mListeners; - // Reset all state values in map to default state. - void handleReset(const int64_t eventTimeNs); + // Reset all state values in map to the given state. + void handleReset(const int64_t eventTimeNs, const int32_t newState); - // Reset only the state value mapped to the given primary key to default state. - // Partial resets are used when we only need to update the state of one primary - // key instead of clearing/reseting every key in the map. - void handlePartialReset(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey); + // Clears the state value mapped to the given primary key by setting it to kStateUnknown. + void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey); // Update the StateMap based on the received state value. - // Store the old and new states. - void updateState(const HashableDimensionKey& primaryKey, const int32_t eventState, - int32_t* oldState, int32_t* newState); + void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, + const int32_t newState, const bool nested, + StateValueInfo* stateValueInfo); + + // Notify registered state listeners of state change. + void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, + const int32_t oldState, const int32_t newState); }; +bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output); + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 77a3eb31fdd4..563531351fe0 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -549,14 +549,13 @@ int64_t getWallClockMillis() { return time(nullptr) * MS_PER_SEC; } -int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs) { - if (AtomsInfo::kTruncatingTimestampAtomBlackList.find(atomId) != - AtomsInfo::kTruncatingTimestampAtomBlackList.end() || - (atomId >= StatsdStats::kTimestampTruncationStartTag && - atomId <= StatsdStats::kTimestampTruncationEndTag)) { - return timestampNs / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60); +int64_t truncateTimestampIfNecessary(const LogEvent& event) { + if (event.shouldTruncateTimestamp() || + (event.GetTagId() >= StatsdStats::kTimestampTruncationStartTag && + event.GetTagId() <= StatsdStats::kTimestampTruncationEndTag)) { + return event.GetElapsedTimestampNs() / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60); } else { - return timestampNs; + return event.GetElapsedTimestampNs(); } } diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index ade25d69a3a4..20d93b5a5365 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -17,11 +17,12 @@ #pragma once #include <android/util/ProtoOutputStream.h> + #include "FieldValue.h" #include "HashableDimensionKey.h" -#include "atoms_info.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" using android::util::ProtoOutputStream; @@ -93,9 +94,9 @@ bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) { return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); } -// Checks the blacklist of atoms as well as the blacklisted range of 300,000 - 304,999. +// Checks the truncate timestamp annotation as well as the blacklisted range of 300,000 - 304,999. // Returns the truncated timestamp to the nearest 5 minutes if needed. -int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs); +int64_t truncateTimestampIfNecessary(const LogEvent& event); // Checks permission for given pid and uid. bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid); diff --git a/cmds/statsd/src/utils/NamedLatch.cpp b/cmds/statsd/src/utils/NamedLatch.cpp new file mode 100644 index 000000000000..6e77977857cc --- /dev/null +++ b/cmds/statsd/src/utils/NamedLatch.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define DEBUG false // STOPSHIP if true + +#include "NamedLatch.h" + +using namespace std; + +namespace android { +namespace os { +namespace statsd { + +NamedLatch::NamedLatch(const set<string>& eventNames) : mRemainingEventNames(eventNames) { +} + +void NamedLatch::countDown(const string& eventName) { + bool notify = false; + { + lock_guard<mutex> lg(mMutex); + mRemainingEventNames.erase(eventName); + notify = mRemainingEventNames.empty(); + } + if (notify) { + mConditionVariable.notify_all(); + } +} + +void NamedLatch::wait() const { + unique_lock<mutex> unique_lk(mMutex); + mConditionVariable.wait(unique_lk, [this] { return mRemainingEventNames.empty(); }); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/utils/NamedLatch.h b/cmds/statsd/src/utils/NamedLatch.h new file mode 100644 index 000000000000..70238370f647 --- /dev/null +++ b/cmds/statsd/src/utils/NamedLatch.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <gtest/gtest_prod.h> + +#include <condition_variable> +#include <mutex> +#include <set> + +namespace android { +namespace os { +namespace statsd { + +/** + * This class provides a threading primitive similar to a latch. + * The primary difference is that it waits for named events to occur instead of waiting for + * N threads to reach a certain point. + * + * It uses a condition variable under the hood. + */ +class NamedLatch { +public: + explicit NamedLatch(const std::set<std::string>& eventNames); + + NamedLatch(const NamedLatch&) = delete; + NamedLatch& operator=(const NamedLatch&) = delete; + + // Mark a specific event as completed. If this event has called countDown already or if the + // event was not specified in the constructor, the function is a no-op. + void countDown(const std::string& eventName); + + // Blocks the calling thread until all events in eventNames have called countDown. + void wait() const; + +private: + mutable std::mutex mMutex; + mutable std::condition_variable mConditionVariable; + std::set<std::string> mRemainingEventNames; + + FRIEND_TEST(NamedLatchTest, TestCountDownCalledBySameEventName); +}; +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp index a5da9c8a6f56..b1461a1535ba 100644 --- a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp @@ -232,7 +232,7 @@ TEST(CountMetricE2eTest, TestSlicedStateWithMap) { StateMap map = state.map(); for (auto group : map.group()) { for (auto value : group.value()) { - EXPECT_EQ(metricProducer->mStateGroupMap[SCREEN_STATE_ATOM_ID][value], + EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), group.group_id()); } } @@ -614,7 +614,7 @@ TEST(CountMetricE2eTest, TestMultipleSlicedStates) { StateMap map = state1.map(); for (auto group : map.group()) { for (auto value : group.value()) { - EXPECT_EQ(metricProducer->mStateGroupMap[SCREEN_STATE_ATOM_ID][value], + EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), group.group_id()); } } diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp index ba09a353e8b6..d59ec3ef4a29 100644 --- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp @@ -921,18 +921,18 @@ TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) { bucket #1 bucket #2 | 1 2 3 4 5 6 7 8 (minutes) |---------------------------------------|------------------ - ON OFF ON (BatterySaverMode) + ON OFF ON (BatterySaverMode) T F T (DeviceUnpluggedPredicate) - | | | (ScreenIsOnEvent) + | | | (ScreenIsOnEvent) | | | (ScreenIsOffEvent) | (ScreenDozeEvent) */ // Initialize log events. std::vector<std::unique_ptr<LogEvent>> events; - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 60 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:10 + bucketStartTimeNs + 20 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:30 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 60 * NS_PER_SEC)); // 1:10 events.push_back(CreateScreenStateChangedEvent( bucketStartTimeNs + 80 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30 diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index 78c80bc8307c..ba2a4cfbe8fd 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -19,6 +19,7 @@ #include "state/StateListener.h" #include "state/StateManager.h" +#include "state/StateTracker.h" #include "stats_event.h" #include "tests/statsd_test_util.h" @@ -127,23 +128,23 @@ TEST(StateTrackerTest, TestRegisterListener) { // Register listener to non-existing StateTracker EXPECT_EQ(0, mgr.getStateTrackersCount()); - EXPECT_TRUE(mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1)); + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); EXPECT_EQ(1, mgr.getStateTrackersCount()); EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); // Register listener to existing StateTracker - EXPECT_TRUE(mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2)); + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); EXPECT_EQ(1, mgr.getStateTrackersCount()); EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); // Register already registered listener to existing StateTracker - EXPECT_TRUE(mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2)); + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); EXPECT_EQ(1, mgr.getStateTrackersCount()); EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); // Register listener to non-state atom - EXPECT_FALSE(mgr.registerListener(util::BATTERY_LEVEL_CHANGED, listener2)); - EXPECT_EQ(1, mgr.getStateTrackersCount()); + mgr.registerListener(util::BATTERY_LEVEL_CHANGED, listener2); + EXPECT_EQ(2, mgr.getStateTrackersCount()); } /** @@ -249,6 +250,9 @@ TEST(StateTrackerTest, TestStateChangeReset) { EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); + FieldValue stateFieldValue; + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); listener->updates.clear(); std::unique_ptr<LogEvent> event2 = @@ -258,6 +262,8 @@ TEST(StateTrackerTest, TestStateChangeReset) { EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(2000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); listener->updates.clear(); std::unique_ptr<LogEvent> event3 = @@ -265,8 +271,12 @@ TEST(StateTrackerTest, TestStateChangeReset) { BleScanStateChanged::RESET, false, false, false); mgr.onLogEvent(*event3); EXPECT_EQ(2, listener->updates.size()); - EXPECT_EQ(BleScanStateChanged::OFF, listener->updates[0].mState); - EXPECT_EQ(BleScanStateChanged::OFF, listener->updates[1].mState); + for (const TestStateListener::Update& update : listener->updates) { + EXPECT_EQ(BleScanStateChanged::OFF, update.mState); + + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, update.mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::OFF, stateFieldValue.mValue.int_value); + } } /** @@ -352,13 +362,13 @@ TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { // No state stored for this query key. HashableDimensionKey queryKey2; getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); - EXPECT_EQ(WakelockStateChanged::RELEASE, + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey2)); // Partial query fails. HashableDimensionKey queryKey3; getPartialWakelockKey(1001 /* uid */, &queryKey3); - EXPECT_EQ(WakelockStateChanged::RELEASE, + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey3)); } diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 687014f6a298..7216e1d8cc8e 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -569,6 +569,8 @@ std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); @@ -662,9 +664,14 @@ std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs, AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); AStatsEvent_writeString(statsEvent, wakelockName.c_str()); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); @@ -803,7 +810,11 @@ std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent( AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); @@ -821,10 +832,20 @@ std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs, AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_writeInt32(statsEvent, filtered); // filtered - AStatsEvent_writeInt32(statsEvent, firstMatch); // first match - AStatsEvent_writeInt32(statsEvent, opportunistic); // opportunistic + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); + if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) { + AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET, + util::BLE_SCAN_STATE_CHANGED__STATE__OFF); + } + AStatsEvent_writeBool(statsEvent, filtered); // filtered + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, firstMatch); // first match + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, opportunistic); // opportunistic + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); @@ -840,9 +861,14 @@ std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, co AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); AStatsEvent_writeString(statsEvent, packageName.c_str()); - AStatsEvent_writeInt32(statsEvent, usingAlertWindow); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, usingAlertWindow); AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); diff --git a/cmds/statsd/tests/utils/NamedLatch_test.cpp b/cmds/statsd/tests/utils/NamedLatch_test.cpp new file mode 100644 index 000000000000..de48a133823e --- /dev/null +++ b/cmds/statsd/tests/utils/NamedLatch_test.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "utils/NamedLatch.h" + +#include <gtest/gtest.h> + +#include <chrono> +#include <set> +#include <thread> +#include <vector> + +#ifdef __ANDROID__ + +using namespace std; +using std::this_thread::sleep_for; + +namespace android { +namespace os { +namespace statsd { + +TEST(NamedLatchTest, TestWait) { + int numEvents = 5; + string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; + set<string> eventNames = {t1, t2, t3, t4, t5}; + + NamedLatch latch(eventNames); + vector<thread> threads; + vector<bool> done(numEvents, false); + + int i = 0; + for (const string& eventName : eventNames) { + threads.emplace_back([&done, &eventName, &latch, i] { + sleep_for(chrono::milliseconds(3)); + done[i] = true; + latch.countDown(eventName); + }); + i++; + } + + latch.wait(); + + for (i = 0; i < numEvents; i++) { + EXPECT_EQ(done[i], 1); + } + + for (i = 0; i < numEvents; i++) { + threads[i].join(); + } +} + +TEST(NamedLatchTest, TestNoWorkers) { + NamedLatch latch({}); + latch.wait(); + // Ensure that latch does not wait if no events need to countDown. +} + +TEST(NamedLatchTest, TestCountDownCalledBySameEventName) { + string t1 = "t1", t2 = "t2"; + set<string> eventNames = {t1, t2}; + + NamedLatch latch(eventNames); + + thread waiterThread([&latch] { latch.wait(); }); + + latch.countDown(t1); + latch.countDown(t1); + + // Ensure that the latch's remaining threads still has t2. + latch.mMutex.lock(); + ASSERT_EQ(latch.mRemainingEventNames.size(), 1); + EXPECT_NE(latch.mRemainingEventNames.find(t2), latch.mRemainingEventNames.end()); + latch.mMutex.unlock(); + + latch.countDown(t2); + waiterThread.join(); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 3a708a6f699b..8e0d939487e3 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2586,6 +2586,44 @@ public class AppOpsManager { } /** + * Returns a listenerId suitable for use with {@link #noteOp(int, int, String, String, String)}. + * + * This is intended for use client side, when the receiver id must be created before the + * associated call is made to the system server. If using {@link PendingIntent} as the receiver, + * avoid using this method as it will include a pointless additional x-process call. Instead to + * prefer passing the PendingIntent to the system server, and then invoking + * {@link #toReceiverId(PendingIntent)} instead. + * + * @param obj the receiver in use + * @return a string representation of the receiver suitable for app ops use + * @hide + */ + // TODO: this should probably be @SystemApi as well + public static @NonNull String toReceiverId(@NonNull Object obj) { + if (obj instanceof PendingIntent) { + return toReceiverId((PendingIntent) obj); + } else { + return obj.getClass().getName() + "@" + System.identityHashCode(obj); + } + } + + /** + * Returns a listenerId suitable for use with {@link #noteOp(int, int, String, String, String)}. + * + * This is intended for use server side, where ActivityManagerService can be referenced without + * an additional x-process call. + * + * @param pendingIntent the pendingIntent in use + * @return a string representation of the pending intent suitable for app ops use + * @see #toReceiverId(Object) + * @hide + */ + // TODO: this should probably be @SystemApi as well + public static @NonNull String toReceiverId(@NonNull PendingIntent pendingIntent) { + return pendingIntent.getTag(""); + } + + /** * When to not enforce {@link #setUserRestriction restrictions}. * * @hide diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index ab868604dfde..344a4d79b766 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -20,6 +20,10 @@ }, { "file_patterns": ["(/|^)AppOpsManager.java"], + "name": "UidAtomTests:testAppOps" + }, + { + "file_patterns": ["(/|^)AppOpsManager.java"], "name": "FrameworksServicesTests", "options": [ { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index fb9adb730314..41e2dc0de4d6 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3666,6 +3666,28 @@ public class DevicePolicyManager { } /** + * Returns whether the given user's credential will be sufficient for all password policy + * requirement, once the user's profile has switched to unified challenge. + * + * <p>This is different from {@link #isActivePasswordSufficient()} since once the profile + * switches to unified challenge, policies set explicitly on the profile will start to affect + * the parent user. + * @param userHandle the user whose password requirement will be checked + * @param profileUser the profile user whose lockscreen challenge will be unified. + * @hide + */ + public boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser) { + if (mService != null) { + try { + return mService.isPasswordSufficientAfterProfileUnification(userHandle, + profileUser); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + /** * Retrieve the number of times the user has failed at entering a password since that last * successful password entry. * <p> diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 591a3f68eed0..d10153c11723 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -85,6 +85,7 @@ interface IDevicePolicyManager { boolean isActivePasswordSufficient(int userHandle, boolean parent); boolean isProfileActivePasswordSufficientForParent(int userHandle); + boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser); int getPasswordComplexity(boolean parent); boolean isUsingUnifiedPassword(in ComponentName admin); int getCurrentFailedPasswordAttempts(int userHandle, boolean parent); diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 86ebb47400c7..39e1f0dc2d2c 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -350,7 +350,7 @@ public final class PasswordMetrics implements Parcelable { * * TODO: move to PasswordPolicy */ - private void maxWith(PasswordMetrics other) { + public void maxWith(PasswordMetrics other) { credType = Math.max(credType, other.credType); if (credType != CREDENTIAL_TYPE_PASSWORD) { return; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index f216db6fc717..29a98faf5cd1 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -980,16 +980,10 @@ public final class BluetoothAdapter { @Override protected Integer recompute(Void query) { try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getState(); - } + return mService.getState(); } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); + throw e.rethrowFromSystemServer(); } - return BluetoothAdapter.STATE_OFF; } }; @@ -1004,6 +998,30 @@ public final class BluetoothAdapter { } /** + * Fetch the current bluetooth state. If the service is down, return + * OFF. + */ + @AdapterState + private int getStateInternal() { + int state = BluetoothAdapter.STATE_OFF; + try { + mServiceLock.readLock().lock(); + if (mService != null) { + state = mBluetoothGetStateCache.query(null); + } + } catch (RuntimeException e) { + if (e.getCause() instanceof RemoteException) { + Log.e(TAG, "", e.getCause()); + } else { + throw e; + } + } finally { + mServiceLock.readLock().unlock(); + } + return state; + } + + /** * Get the current state of the local Bluetooth adapter. * <p>Possible return values are * {@link #STATE_OFF}, @@ -1016,7 +1034,7 @@ public final class BluetoothAdapter { @RequiresPermission(Manifest.permission.BLUETOOTH) @AdapterState public int getState() { - int state = mBluetoothGetStateCache.query(null); + int state = getStateInternal(); // Consider all internal states as OFF if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON @@ -1054,7 +1072,7 @@ public final class BluetoothAdapter { @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine " + "whether you can use BLE & BT classic.") public int getLeState() { - int state = mBluetoothGetStateCache.query(null); + int state = getStateInternal(); if (VDBG) { Log.d(TAG, "getLeState() returning " + BluetoothAdapter.nameForState(state)); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9ca2db970eb7..d36d583559a0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3158,6 +3158,23 @@ public abstract class PackageManager { "android.content.pm.extra.VERIFICATION_LONG_VERSION_CODE"; /** + * Extra field name for the Merkle tree root hash of a package. + * <p>Passed to a package verifier both prior to verification and as a result + * of verification. + * <p>The value of the extra is a specially formatted list: + * {@code filename1:HASH_1;filename2:HASH_2;...;filenameN:HASH_N} + * <p>The extra must include an entry for every APK within an installation. If + * a hash is not physically present, a hash value of {@code 0} will be used. + * <p>The root hash is generated using SHA-256, no salt with a 4096 byte block + * size. See the description of the + * <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#merkle-tree">fs-verity merkle-tree</a> + * for more details. + * @hide + */ + public static final String EXTRA_VERIFICATION_ROOT_HASH = + "android.content.pm.extra.EXTRA_VERIFICATION_ROOT_HASH"; + + /** * Extra field name for the ID of a intent filter pending verification. * Passed to an intent filter verifier and is used to call back to * {@link #verifyIntentFilter} diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 4c9553249a0c..2ee0ad67b108 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -34,6 +34,7 @@ import android.content.pm.parsing.component.ParsedProvider; import android.content.pm.parsing.component.ParsedService; import android.os.Bundle; import android.util.SparseArray; +import android.util.SparseIntArray; import java.security.PublicKey; import java.util.Map; @@ -258,6 +259,8 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setManageSpaceActivityName(String manageSpaceActivityName); + ParsingPackage setMinExtensionVersions(@Nullable SparseIntArray minExtensionVersions); + ParsingPackage setMinSdkVersion(int minSdkVersion); ParsingPackage setNetworkSecurityConfigRes(int networkSecurityConfigRes); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index be1817d09155..1a1395ca7e9e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -51,6 +51,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -340,6 +341,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private String manageSpaceActivityName; private float maxAspectRatio; private float minAspectRatio; + @Nullable + private SparseIntArray minExtensionVersions; private int minSdkVersion; private int networkSecurityConfigRes; @Nullable @@ -1100,6 +1103,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { dest.writeBoolean(this.preserveLegacyExternalStorage); dest.writeArraySet(this.mimeGroups); dest.writeInt(this.gwpAsanMode); + dest.writeSparseIntArray(this.minExtensionVersions); } public ParsingPackageImpl(Parcel in) { @@ -1259,6 +1263,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.preserveLegacyExternalStorage = in.readBoolean(); this.mimeGroups = (ArraySet<String>) in.readArraySet(boot); this.gwpAsanMode = in.readInt(); + this.minExtensionVersions = in.readSparseIntArray(); } public static final Parcelable.Creator<ParsingPackageImpl> CREATOR = @@ -1767,6 +1772,12 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { return minAspectRatio; } + @Nullable + @Override + public SparseIntArray getMinExtensionVersions() { + return minExtensionVersions; + } + @Override public int getMinSdkVersion() { return minSdkVersion; @@ -2215,6 +2226,12 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public ParsingPackageImpl setMinExtensionVersions(@Nullable SparseIntArray value) { + minExtensionVersions = value; + return this; + } + + @Override public ParsingPackageImpl setMinSdkVersion(int value) { minSdkVersion = value; return this; diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index 687bc235cfa7..1ded8d40c727 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -41,6 +41,7 @@ import android.os.Parcelable; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.internal.R; @@ -609,6 +610,13 @@ public interface ParsingPackageRead extends Parcelable { String getManageSpaceActivityName(); /** + * @see ApplicationInfo#minExtensionVersions + * @see R.styleable#AndroidManifestExtensionSdk + */ + @Nullable + SparseIntArray getMinExtensionVersions(); + + /** * @see ApplicationInfo#minSdkVersion * @see R.styleable#AndroidManifestUsesSdk_minSdkVersion */ diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index c61362feaeae..29ece4924e5c 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -95,6 +95,7 @@ import android.util.DisplayMetrics; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.TypedValue; import android.util.apk.ApkSignatureVerifier; @@ -1255,6 +1256,7 @@ public class ParsingPackageUtils { int type; final int innerDepth = parser.getDepth(); + SparseIntArray minExtensionVersions = null; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -1263,7 +1265,10 @@ public class ParsingPackageUtils { final ParseResult result; if (parser.getName().equals("extension-sdk")) { - result = parseExtensionSdk(input, pkg, res, parser); + if (minExtensionVersions == null) { + minExtensionVersions = new SparseIntArray(); + } + result = parseExtensionSdk(input, res, parser, minExtensionVersions); XmlUtils.skipCurrentTag(parser); } else { result = ParsingUtils.unknownTag("<uses-sdk>", pkg, parser, input); @@ -1273,6 +1278,7 @@ public class ParsingPackageUtils { return input.error(result); } } + pkg.setMinExtensionVersions(exactSizedCopyOfSparseArray(minExtensionVersions)); } finally { sa.recycle(); } @@ -1280,8 +1286,21 @@ public class ParsingPackageUtils { return input.success(pkg); } - private static ParseResult parseExtensionSdk(ParseInput input, ParsingPackage pkg, - Resources res, XmlResourceParser parser) { + @Nullable + private static SparseIntArray exactSizedCopyOfSparseArray(@Nullable SparseIntArray input) { + if (input == null) { + return null; + } + SparseIntArray output = new SparseIntArray(input.size()); + for (int i = 0; i < input.size(); i++) { + output.put(input.keyAt(i), input.valueAt(i)); + } + return output; + } + + private static ParseResult<SparseIntArray> parseExtensionSdk( + ParseInput input, Resources res, XmlResourceParser parser, + SparseIntArray minExtensionVersions) { int sdkVersion; int minVersion; TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestExtensionSdk); @@ -1316,7 +1335,8 @@ public class ParsingPackageUtils { PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "Specified sdkVersion " + sdkVersion + " is not valid"); } - return input.success(pkg); + minExtensionVersions.put(sdkVersion, minVersion); + return input.success(minExtensionVersions); } /** diff --git a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java index 882a7f4ab37d..b3b4549426f0 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java @@ -233,8 +233,10 @@ public class LegacyFaceDetectMapper { Camera.Parameters params = legacyRequest.parameters; Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArray, - request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params); + ZoomData zoomData = ParameterUtils.convertToLegacyZoom(activeArray, + request.get(CaptureRequest.SCALER_CROP_REGION), + request.get(CaptureRequest.CONTROL_ZOOM_RATIO), + previewSize, params); List<Face> convertedFaces = new ArrayList<>(); if (faces != null) { diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index 6953a5b793c3..362ddfae67bf 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -771,6 +771,7 @@ public class LegacyMetadataMapper { CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES , CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE , CameraCharacteristics.CONTROL_MAX_REGIONS , + CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE , CameraCharacteristics.FLASH_INFO_AVAILABLE , CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL , CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES , @@ -828,6 +829,7 @@ public class LegacyMetadataMapper { CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_SCENE_MODE, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, + CaptureRequest.CONTROL_ZOOM_RATIO, CaptureRequest.FLASH_MODE, CaptureRequest.JPEG_GPS_COORDINATES, CaptureRequest.JPEG_GPS_PROCESSING_METHOD, @@ -872,6 +874,7 @@ public class LegacyMetadataMapper { CaptureResult.CONTROL_AWB_MODE , CaptureResult.CONTROL_AWB_LOCK , CaptureResult.CONTROL_MODE , + CaptureResult.CONTROL_ZOOM_RATIO , CaptureResult.FLASH_MODE , CaptureResult.JPEG_GPS_COORDINATES , CaptureResult.JPEG_GPS_PROCESSING_METHOD , @@ -935,6 +938,12 @@ public class LegacyMetadataMapper { private static void mapScaler(CameraMetadataNative m, Parameters p) { /* + * control.zoomRatioRange + */ + Range<Float> zoomRatioRange = new Range<Float>(1.0f, ParameterUtils.getMaxZoomRatio(p)); + m.set(CONTROL_ZOOM_RATIO_RANGE, zoomRatioRange); + + /* * scaler.availableMaxDigitalZoom */ m.set(SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, ParameterUtils.getMaxZoomRatio(p)); @@ -1383,6 +1392,9 @@ public class LegacyMetadataMapper { // control.sceneMode -- DISABLED is always available m.set(CaptureRequest.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED); + // control.zoomRatio -- 1.0 + m.set(CaptureRequest.CONTROL_ZOOM_RATIO, 1.0f); + /* * statistics.* */ diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java index 2e06d5fb3b6f..3a46379477e9 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java @@ -68,8 +68,9 @@ public class LegacyRequestMapper { */ ParameterUtils.ZoomData zoomData; { - zoomData = ParameterUtils.convertScalerCropRegion(activeArray, + zoomData = ParameterUtils.convertToLegacyZoom(activeArray, request.get(SCALER_CROP_REGION), + request.get(CONTROL_ZOOM_RATIO), previewSize, params); diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java index dc5823d80f21..09edf74f0d4c 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java @@ -118,8 +118,10 @@ public class LegacyResultMapper { Rect activeArraySize = characteristics.get( CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArraySize, - request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params); + ZoomData zoomData = ParameterUtils.convertToLegacyZoom(activeArraySize, + request.get(CaptureRequest.SCALER_CROP_REGION), + request.get(CaptureRequest.CONTROL_ZOOM_RATIO), + previewSize, params); /* * colorCorrection @@ -516,5 +518,12 @@ public class LegacyResultMapper { { m.set(SCALER_CROP_REGION, zoomData.reportedCrop); } + + /* + * control.zoomRatio + */ + { + m.set(CONTROL_ZOOM_RATIO, zoomData.reportedZoomRatio); + } } } diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java index 3cfd020aeee3..eb435989e9a0 100644 --- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java +++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java @@ -73,11 +73,15 @@ public class ParameterUtils { public final Rect previewCrop; /** Reported crop-region given the zoom index, coordinates relative to active-array */ public final Rect reportedCrop; + /** Reported zoom ratio given the zoom index */ + public final float reportedZoomRatio; - public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop) { + public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop, + float reportedZoomRatio) { this.zoomIndex = zoomIndex; this.previewCrop = previewCrop; this.reportedCrop = reportedCrop; + this.reportedZoomRatio = reportedZoomRatio; } } @@ -371,7 +375,8 @@ public class ParameterUtils { * @throws NullPointerException if any of the args were {@code null} */ public static int getClosestAvailableZoomCrop( - Camera.Parameters params, Rect activeArray, Size streamSize, Rect cropRegion, + Camera.Parameters params, Rect activeArray, + Size streamSize, Rect cropRegion, /*out*/ Rect reportedCropRegion, Rect previewCropRegion) { @@ -733,6 +738,92 @@ public class ParameterUtils { } /** + * Convert the user-specified crop region/zoom into zoom data; which can be used + * to set the parameters to a specific zoom index, or to report back to the user what + * the actual zoom was, or for other calculations requiring the current preview crop region. + * + * <p>None of the parameters are mutated.<p> + * + * @param activeArraySize active array size of the sensor (e.g. max jpeg size) + * @param cropRegion the user-specified crop region + * @param zoomRatio the user-specified zoom ratio + * @param previewSize the current preview size (in pixels) + * @param params the current camera parameters (not mutated) + * + * @return the zoom index, and the effective/reported crop regions (relative to active array) + */ + public static ZoomData convertToLegacyZoom(Rect activeArraySize, Rect + cropRegion, Float zoomRatio, Size previewSize, Camera.Parameters params) { + final float FLOAT_EQUAL_THRESHOLD = 0.0001f; + if (zoomRatio != null && + Math.abs(1.0f - zoomRatio) > FLOAT_EQUAL_THRESHOLD) { + // User uses CONTROL_ZOOM_RATIO to control zoom + return convertZoomRatio(activeArraySize, zoomRatio, previewSize, params); + } + + return convertScalerCropRegion(activeArraySize, cropRegion, previewSize, params); + } + + /** + * Convert the user-specified zoom ratio into zoom data; which can be used + * to set the parameters to a specific zoom index, or to report back to the user what the + * actual zoom was, or for other calculations requiring the current preview crop region. + * + * <p>None of the parameters are mutated.</p> + * + * @param activeArraySize active array size of the sensor (e.g. max jpeg size) + * @param zoomRatio the current zoom ratio + * @param previewSize the current preview size (in pixels) + * @param params the current camera parameters (not mutated) + * + * @return the zoom index, and the effective/reported crop regions (relative to active array) + */ + public static ZoomData convertZoomRatio(Rect activeArraySize, float zoomRatio, + Size previewSize, Camera.Parameters params) { + if (DEBUG) { + Log.v(TAG, "convertZoomRatio - user zoom ratio was " + zoomRatio); + } + + List<Rect> availableReportedCropRegions = + getAvailableZoomCropRectangles(params, activeArraySize); + List<Rect> availablePreviewCropRegions = + getAvailablePreviewZoomCropRectangles(params, activeArraySize, previewSize); + if (availableReportedCropRegions.size() != availablePreviewCropRegions.size()) { + throw new AssertionError("available reported/preview crop region size mismatch"); + } + + // Find the best matched legacy zoom ratio for the requested camera2 zoom ratio. + int bestZoomIndex = 0; + Rect reportedCropRegion = new Rect(availableReportedCropRegions.get(0)); + Rect previewCropRegion = new Rect(availablePreviewCropRegions.get(0)); + float reportedZoomRatio = 1.0f; + if (params.isZoomSupported()) { + List<Integer> zoomRatios = params.getZoomRatios(); + for (int i = 1; i < zoomRatios.size(); i++) { + if (zoomRatio * ZOOM_RATIO_MULTIPLIER >= zoomRatios.get(i)) { + bestZoomIndex = i; + reportedCropRegion = availableReportedCropRegions.get(i); + previewCropRegion = availablePreviewCropRegions.get(i); + reportedZoomRatio = zoomRatios.get(i); + } else { + break; + } + } + } + + if (DEBUG) { + Log.v(TAG, "convertZoomRatio - zoom calculated to: " + + "zoomIndex = " + bestZoomIndex + + ", reported crop region = " + reportedCropRegion + + ", preview crop region = " + previewCropRegion + + ", reported zoom ratio = " + reportedZoomRatio); + } + + return new ZoomData(bestZoomIndex, reportedCropRegion, + previewCropRegion, reportedZoomRatio); + } + + /** * Convert the user-specified crop region into zoom data; which can be used * to set the parameters to a specific zoom index, or to report back to the user what the * actual zoom was, or for other calculations requiring the current preview crop region. @@ -767,15 +858,17 @@ public class ParameterUtils { final int zoomIdx = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly, previewSize, userCropRegion, /*out*/reportedCropRegion, /*out*/previewCropRegion); + final float reportedZoomRatio = 1.0f; if (DEBUG) { Log.v(TAG, "convertScalerCropRegion - zoom calculated to: " + "zoomIndex = " + zoomIdx + ", reported crop region = " + reportedCropRegion + - ", preview crop region = " + previewCropRegion); + ", preview crop region = " + previewCropRegion + + ", reported zoom ratio = " + reportedZoomRatio); } - return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion); + return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion, reportedZoomRatio); } /** diff --git a/core/java/android/net/UrlQuerySanitizer.java b/core/java/android/net/UrlQuerySanitizer.java index cf08b653d0f4..b1cf044e8173 100644 --- a/core/java/android/net/UrlQuerySanitizer.java +++ b/core/java/android/net/UrlQuerySanitizer.java @@ -306,7 +306,7 @@ public class UrlQuerySanitizer { return null; } int length = value.length(); - if ((mFlags & SCRIPT_URL_OK) != 0) { + if ((mFlags & SCRIPT_URL_OK) == 0) { if (length >= MIN_SCRIPT_PREFIX_LENGTH) { String asLower = value.toLowerCase(Locale.ROOT); if (asLower.startsWith(JAVASCRIPT_PREFIX) || diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 0f2060a36ac9..187274a837a0 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -57,6 +57,7 @@ import android.view.WindowManager.LayoutParams; import com.android.internal.R; import com.android.internal.os.RoSystemProperties; +import com.android.internal.util.FrameworkStatsLog; import java.io.IOException; import java.lang.annotation.Retention; @@ -1841,6 +1842,35 @@ public class UserManager { } /** + * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the + * user type. + * @hide + */ + public static int getUserTypeForStatsd(@NonNull String userType) { + switch (userType) { + case USER_TYPE_FULL_SYSTEM: + return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM; + case USER_TYPE_FULL_SECONDARY: + return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY; + case USER_TYPE_FULL_GUEST: + return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST; + case USER_TYPE_FULL_DEMO: + return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO; + case USER_TYPE_FULL_RESTRICTED: + return FrameworkStatsLog + .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED; + case USER_TYPE_PROFILE_MANAGED: + return FrameworkStatsLog + .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED; + case USER_TYPE_SYSTEM_HEADLESS: + return FrameworkStatsLog + .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS; + default: + return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN; + } + } + + /** * @hide * @deprecated Use {@link #isRestrictedProfile()} */ diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 5cc73caa4f60..d35ce5b2c3f8 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -16,6 +16,8 @@ package android.os.incremental; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.ParcelFileDescriptor; import java.io.ByteArrayInputStream; @@ -45,8 +47,8 @@ public class V4Signature { public static class HashingInfo { public final int hashAlgorithm; // only 1 == SHA256 supported public final byte log2BlockSize; // only 12 (block size 4096) supported now - public final byte[] salt; // used exactly as in fs-verity, 32 bytes max - public final byte[] rawRootHash; // salted digest of the first Merkle tree page + @Nullable public final byte[] salt; // used exactly as in fs-verity, 32 bytes max + @Nullable public final byte[] rawRootHash; // salted digest of the first Merkle tree page HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) { this.hashAlgorithm = hashAlgorithm; @@ -58,7 +60,8 @@ public class V4Signature { /** * Constructs HashingInfo from byte array. */ - public static HashingInfo fromByteArray(byte[] bytes) throws IOException { + @NonNull + public static HashingInfo fromByteArray(@NonNull byte[] bytes) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); final int hashAlgorithm = buffer.getInt(); final byte log2BlockSize = buffer.get(); @@ -106,8 +109,18 @@ public class V4Signature { } public final int version; // Always 2 for now. - public final byte[] hashingInfo; - public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later. + /** + * Raw byte array containing the IncFS hashing data. + * @see HashingInfo#fromByteArray(byte[]) + */ + @Nullable public final byte[] hashingInfo; + + /** + * Raw byte array containing the V4 signature data. + * <p>Passed as-is to the kernel. Can be retrieved later. + * @see SigningInfo#fromByteArray(byte[]) + */ + @Nullable public final byte[] signingInfo; /** * Construct a V4Signature from .idsig file. @@ -121,7 +134,8 @@ public class V4Signature { /** * Construct a V4Signature from a byte array. */ - public static V4Signature readFrom(byte[] bytes) throws IOException { + @NonNull + public static V4Signature readFrom(@NonNull byte[] bytes) throws IOException { try (InputStream stream = new ByteArrayInputStream(bytes)) { return readFrom(stream); } @@ -169,7 +183,7 @@ public class V4Signature { return this.version == SUPPORTED_VERSION; } - private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) { + private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfo) { this.version = version; this.hashingInfo = hashingInfo; this.signingInfo = signingInfo; diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java index 63b380404217..9cf1b87f7eab 100644 --- a/core/java/android/service/autofill/InlinePresentation.java +++ b/core/java/android/service/autofill/InlinePresentation.java @@ -19,7 +19,6 @@ package android.service.autofill; import android.annotation.NonNull; import android.annotation.Size; import android.app.slice.Slice; -import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.widget.inline.InlinePresentationSpec; @@ -67,18 +66,6 @@ public final class InlinePresentation implements Parcelable { return hints.toArray(new String[hints.size()]); } - /** - * @hide - * @removed - */ - @UnsupportedAppUsage - public InlinePresentation( - @NonNull Slice slice, - @NonNull android.view.inline.InlinePresentationSpec inlinePresentationSpec, - boolean pinned) { - this(slice, inlinePresentationSpec.toWidget(), pinned); - } - // Code below generated by codegen v1.0.15. @@ -245,7 +232,7 @@ public final class InlinePresentation implements Parcelable { }; @DataClass.Generated( - time = 1585633564226L, + time = 1586992400667L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java", inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 3f0787350075..337027ef5bc9 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -182,7 +182,6 @@ public class DreamService extends Service implements Window.Callback { private Window mWindow; private Activity mActivity; private boolean mInteractive; - private boolean mLowProfile = true; private boolean mFullscreen; private boolean mScreenBright = true; private boolean mStarted; @@ -530,32 +529,6 @@ public class DreamService extends Service implements Window.Callback { } /** - * Sets View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. - * - * @param lowProfile True to set View.SYSTEM_UI_FLAG_LOW_PROFILE - * @hide There is no reason to have this -- dreams can set this flag - * on their own content view, and from there can actually do the - * correct interactions with it (seeing when it is cleared etc). - */ - public void setLowProfile(boolean lowProfile) { - if (mLowProfile != lowProfile) { - mLowProfile = lowProfile; - int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE; - applySystemUiVisibilityFlags(mLowProfile ? flag : 0, flag); - } - } - - /** - * Returns whether or not this dream is in low profile mode. Defaults to true. - * - * @see #setLowProfile(boolean) - * @hide - */ - public boolean isLowProfile() { - return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_LOW_PROFILE, mLowProfile); - } - - /** * Controls {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN} * on the dream's window. * @@ -1094,10 +1067,6 @@ public class DreamService extends Service implements Window.Callback { // along well. Dreams usually don't need such bars anyways, so disable them by default. mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - applySystemUiVisibilityFlags( - (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0), - View.SYSTEM_UI_FLAG_LOW_PROFILE); - mWindow.getDecorView().addOnAttachStateChangeListener( new View.OnAttachStateChangeListener() { @Override @@ -1126,18 +1095,6 @@ public class DreamService extends Service implements Window.Callback { } } - private boolean getSystemUiVisibilityFlagValue(int flag, boolean defaultValue) { - View v = mWindow == null ? null : mWindow.getDecorView(); - return v == null ? defaultValue : (v.getSystemUiVisibility() & flag) != 0; - } - - private void applySystemUiVisibilityFlags(int flags, int mask) { - View v = mWindow == null ? null : mWindow.getDecorView(); - if (v != null) { - v.setSystemUiVisibility(applyFlags(v.getSystemUiVisibility(), flags, mask)); - } - } - private int applyFlags(int oldFlags, int flags, int mask) { return (oldFlags&~mask) | (flags&mask); } @@ -1163,7 +1120,6 @@ public class DreamService extends Service implements Window.Callback { pw.println(" window: " + mWindow); pw.print(" flags:"); if (isInteractive()) pw.print(" interactive"); - if (isLowProfile()) pw.print(" lowprofile"); if (isFullscreen()) pw.print(" fullscreen"); if (isScreenBright()) pw.print(" bright"); if (isWindowless()) pw.print(" windowless"); diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 21b83c660446..5c43f8f829b0 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -292,6 +292,18 @@ public class StatusBarNotification implements Parcelable { return this.user.getIdentifier(); } + /** + * Like {@link #getUserId()} but handles special users. + * @hide + */ + public int getNormalizedUserId() { + int userId = getUserId(); + if (userId == UserHandle.USER_ALL) { + userId = UserHandle.USER_SYSTEM; + } + return userId; + } + /** The package that the notification belongs to. */ public String getPackageName() { return pkg; diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java index 362b94b83c3f..3b5a6d59e7e6 100644 --- a/core/java/android/speech/RecognizerIntent.java +++ b/core/java/android/speech/RecognizerIntent.java @@ -413,6 +413,10 @@ public class RecognizerIntent { * {@link #ACTION_VOICE_SEARCH_HANDS_FREE}, {@link #ACTION_WEB_SEARCH} to indicate whether to * only use an offline speech recognition engine. The default is false, meaning that either * network or offline recognition engines may be used. + * + * <p>Depending on the recognizer implementation, these values may have + * no effect.</p> + * */ public static final String EXTRA_PREFER_OFFLINE = "android.speech.extra.PREFER_OFFLINE"; } diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index a9f3e04036b5..0c50cb782c24 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -17,7 +17,6 @@ package android.view; import android.annotation.Nullable; -import android.app.AppOpsManager; import android.app.Notification; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -27,7 +26,6 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.util.ArraySet; import android.util.AttributeSet; import android.widget.ImageView; import android.widget.LinearLayout; @@ -396,6 +394,7 @@ public class NotificationHeaderView extends ViewGroup { addRectAroundView(mIcon); mExpandButtonRect = addRectAroundView(mExpandButton); mAppOpsRect = addRectAroundView(mAppOps); + setTouchDelegate(new TouchDelegate(mAppOpsRect, mAppOps)); addWidthRect(); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 1086774fc8ff..76ed37c51bfe 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -228,6 +228,7 @@ public final class SurfaceControl implements Parcelable { */ public long mNativeObject; private long mNativeHandle; + private Throwable mReleaseStack = null; // TODO: Move this to native. private final Object mSizeLock = new Object(); @@ -426,11 +427,18 @@ public final class SurfaceControl implements Parcelable { if (mNativeObject != 0) { release(); } - if (nativeObject != 0) { + if (nativeObject != 0) { mCloseGuard.open("release"); } mNativeObject = nativeObject; mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; + if (mNativeObject == 0) { + if (Build.IS_DEBUGGABLE) { + mReleaseStack = new Throwable("assigned zero nativeObject here"); + } + } else { + mReleaseStack = null; + } } /** @@ -989,11 +997,22 @@ public final class SurfaceControl implements Parcelable { nativeRelease(mNativeObject); mNativeObject = 0; mNativeHandle = 0; + if (Build.IS_DEBUGGABLE) { + mReleaseStack = new Throwable("released here"); + } mCloseGuard.close(); } } /** + * Returns the call stack that assigned mNativeObject to zero. + * @hide + */ + public Throwable getReleaseStack() { + return mReleaseStack; + } + + /** * Disconnect any client still connected to the surface. * @hide */ @@ -1004,8 +1023,11 @@ public final class SurfaceControl implements Parcelable { } private void checkNotReleased() { - if (mNativeObject == 0) throw new NullPointerException( - "mNativeObject is null. Have you called release() already?"); + if (mNativeObject == 0) { + Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack); + throw new NullPointerException( + "mNativeObject of " + this + " is null. Have you called release() already?"); + } } /** diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java index c191a54283fb..fb9d05e2d730 100644 --- a/core/java/android/view/WindowInsetsAnimationController.java +++ b/core/java/android/view/WindowInsetsAnimationController.java @@ -141,7 +141,10 @@ public interface WindowInsetsAnimationController { /** * Finishes the animation, and leaves the windows shown or hidden. * <p> - * After invoking {@link #finish(boolean)}, this instance is no longer {@link #isReady ready}. + * After invoking {@link #finish}, this instance is no longer {@link #isReady ready}. + * <p> + * Note: Finishing an animation implicitly {@link #setInsetsAndAlpha sets insets and alpha} + * according to the requested end state without any further animation. * * @param shown if {@code true}, the windows will be shown after finishing the * animation. Otherwise they will be hidden. diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 83a79344917c..6d3dbfe16b78 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1258,13 +1258,6 @@ public final class AutofillManager { } } - if (mForAugmentedAutofillOnly) { - if (sVerbose) { - Log.v(TAG, "notifyValueChanged(): not notifying system server on " - + "augmented-only mode"); - } - return; - } if (!mEnabled || !isActiveLocked()) { if (!startAutofillIfNeededLocked(view)) { if (sVerbose) { @@ -1299,10 +1292,6 @@ public final class AutofillManager { return; } synchronized (mLock) { - if (mForAugmentedAutofillOnly) { - if (sVerbose) Log.v(TAG, "notifyValueChanged(): ignoring on augmented only mode"); - return; - } if (!mEnabled || !isActiveLocked()) { if (sVerbose) { Log.v(TAG, "notifyValueChanged(" + view.getAutofillId() + ":" + virtualId diff --git a/core/java/android/view/inline/InlineContentView.java b/core/java/android/view/inline/InlineContentView.java deleted file mode 100644 index 3df201c9145d..000000000000 --- a/core/java/android/view/inline/InlineContentView.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.inline; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.PixelFormat; -import android.util.AttributeSet; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.ViewGroup; - -/** - * This class represents a view that holds opaque content from another app that - * you can inline in your UI. - * - * <p>Since the content presented by this view is from another security domain,it is - * shown on a remote surface preventing the host application from accessing that content. - * Also the host application cannot interact with the inlined content by injecting touch - * events or clicking programmatically. - * - * <p>This view can be overlaid by other windows, i.e. redressed, but if this is the case - * the inined UI would not be interactive. Sometimes this is desirable, e.g. animating - * transitions. - * - * <p>By default the surface backing this view is shown on top of the hosting window such - * that the inlined content is interactive. However, you can temporarily move the surface - * under the hosting window which could be useful in some cases, e.g. animating transitions. - * At this point the inlined content will not be interactive and the touch events would - * be delivered to your app. - * - * @hide - * @removed - */ -public class InlineContentView extends ViewGroup { - - /** - * Callback for observing the lifecycle of the surface control - * that manipulates the backing secure embedded UI surface. - */ - public interface SurfaceControlCallback { - /** - * Called when the backing surface is being created. - * - * @param surfaceControl The surface control to manipulate the surface. - */ - void onCreated(@NonNull SurfaceControl surfaceControl); - - /** - * Called when the backing surface is being destroyed. - * - * @param surfaceControl The surface control to manipulate the surface. - */ - void onDestroyed(@NonNull SurfaceControl surfaceControl); - } - - private final @NonNull SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(@NonNull SurfaceHolder holder) { - mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl()); - } - - @Override - public void surfaceChanged(@NonNull SurfaceHolder holder, - int format, int width, int height) { - /* do nothing */ - } - - @Override - public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - mSurfaceControlCallback.onDestroyed(mSurfaceView.getSurfaceControl()); - } - }; - - private final @NonNull SurfaceView mSurfaceView; - - private @Nullable SurfaceControlCallback mSurfaceControlCallback; - - /** - * @inheritDoc - * - * @hide - */ - public InlineContentView(@NonNull Context context) { - this(context, null); - } - - /** - * @inheritDoc - * - * @hide - */ - public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - /** - * @inheritDoc - * - * @hide - */ - public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - /** - * Gets the surface control. If the surface is not created this method - * returns {@code null}. - * - * @return The surface control. - * - * @see #setSurfaceControlCallback(SurfaceControlCallback) - */ - public @Nullable SurfaceControl getSurfaceControl() { - return mSurfaceView.getSurfaceControl(); - } - - /** - * @inheritDoc - * - * @hide - */ - public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes); - mSurfaceView.setZOrderOnTop(true); - mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); - addView(mSurfaceView); - } - - /** - * Sets the embedded UI. - * @param surfacePackage The embedded UI. - * - * @hide - */ - public void setChildSurfacePackage( - @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) { - mSurfaceView.setChildSurfacePackage(surfacePackage); - } - - @Override - public void onLayout(boolean changed, int l, int t, int r, int b) { - mSurfaceView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); - } - - /** - * Sets a callback to observe the lifecycle of the surface control for - * managing the backing surface. - * - * @param callback The callback to set or {@code null} to clear. - */ - public void setSurfaceControlCallback(@Nullable SurfaceControlCallback callback) { - if (mSurfaceControlCallback != null) { - mSurfaceView.getHolder().removeCallback(mSurfaceCallback); - } - mSurfaceControlCallback = callback; - if (mSurfaceControlCallback != null) { - mSurfaceView.getHolder().addCallback(mSurfaceCallback); - } - } - - /** - * @return Whether the surface backing this view appears on top of its parent. - * - * @see #setZOrderedOnTop(boolean) - */ - public boolean isZOrderedOnTop() { - return mSurfaceView.isZOrderedOnTop(); - } - - /** - * Controls whether the backing surface is placed on top of this view's window. - * Normally, it is placed on top of the window, to allow interaction - * with the inlined UI. Via this method, you can place the surface below the - * window. This means that all of the contents of the window this view is in - * will be visible on top of its surface. - * - * <p> The Z ordering can be changed dynamically if the backing surface is - * created, otherwise the ordering would be applied at surface construction time. - * - * @param onTop Whether to show the surface on top of this view's window. - * - * @see #isZOrderedOnTop() - */ - public boolean setZOrderedOnTop(boolean onTop) { - return mSurfaceView.setZOrderedOnTop(onTop, /*allowDynamicChange*/ true); - } -} diff --git a/core/java/android/view/inline/InlinePresentationSpec.java b/core/java/android/view/inline/InlinePresentationSpec.java deleted file mode 100644 index d777cb8d8e0b..000000000000 --- a/core/java/android/view/inline/InlinePresentationSpec.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.inline; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Bundle; -import android.os.Parcelable; -import android.util.Size; - -import com.android.internal.util.DataClass; - -import java.util.ArrayList; -import java.util.List; - -/** - * This class represents the presentation specification by which an inline suggestion - * should abide when constructing its UI. Since suggestions are inlined in a - * host application while provided by another source, they need to be consistent - * with the host's look at feel to allow building smooth and integrated UIs. - * - * @hide - * @removed - */ -@DataClass(genEqualsHashCode = true, genToString = true, genBuilder = true) -public final class InlinePresentationSpec implements Parcelable { - - /** The minimal size of the suggestion. */ - @NonNull - private final Size mMinSize; - /** The maximal size of the suggestion. */ - @NonNull - private final Size mMaxSize; - - /** - * The extras encoding the UI style information. Defaults to {@code Bundle.EMPTY} in which case - * the default system UI style will be used. - */ - @NonNull - private final Bundle mStyle; - - private static Bundle defaultStyle() { - return Bundle.EMPTY; - } - - /** @hide */ - @DataClass.Suppress({"setMaxSize", "setMinSize"}) - abstract static class BaseBuilder { - } - - /** - * @hide - */ - public android.widget.inline.InlinePresentationSpec toWidget() { - final android.widget.inline.InlinePresentationSpec.Builder builder = - new android.widget.inline.InlinePresentationSpec.Builder( - getMinSize(), getMaxSize()); - final Bundle style = getStyle(); - if (style != null) { - builder.setStyle(style); - } - return builder.build(); - } - - /** - * @hide - */ - public static android.view.inline.InlinePresentationSpec fromWidget( - android.widget.inline.InlinePresentationSpec widget) { - final android.view.inline.InlinePresentationSpec.Builder builder = - new android.view.inline.InlinePresentationSpec.Builder( - widget.getMinSize(), widget.getMaxSize()); - final Bundle style = widget.getStyle(); - if (style != null) { - builder.setStyle(style); - } - return builder.build(); - } - - /** - * @hide - */ - public static List<android.view.inline.InlinePresentationSpec> fromWidgets( - List<android.widget.inline.InlinePresentationSpec> widgets) { - final ArrayList<android.view.inline.InlinePresentationSpec> convertedSpecs = - new ArrayList<>(); - for (int i = 0; i < widgets.size(); i++) { - convertedSpecs.add(fromWidget(widgets.get(i))); - } - return convertedSpecs; - } - - - - // Code below generated by codegen v1.0.15. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - /* package-private */ InlinePresentationSpec( - @NonNull Size minSize, - @NonNull Size maxSize, - @NonNull Bundle style) { - this.mMinSize = minSize; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMinSize); - this.mMaxSize = maxSize; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMaxSize); - this.mStyle = style; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mStyle); - - // onConstructed(); // You can define this method to get a callback - } - - /** - * The minimal size of the suggestion. - */ - @UnsupportedAppUsage - @DataClass.Generated.Member - public @NonNull Size getMinSize() { - return mMinSize; - } - - /** - * The maximal size of the suggestion. - */ - @UnsupportedAppUsage - @DataClass.Generated.Member - public @NonNull Size getMaxSize() { - return mMaxSize; - } - - /** - * The extras encoding the UI style information. Defaults to {@code Bundle.EMPTY} in which case - * the default system UI style will be used. - */ - @DataClass.Generated.Member - public @NonNull Bundle getStyle() { - return mStyle; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "InlinePresentationSpec { " + - "minSize = " + mMinSize + ", " + - "maxSize = " + mMaxSize + ", " + - "style = " + mStyle + - " }"; - } - - @Override - @DataClass.Generated.Member - public boolean equals(@Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(InlinePresentationSpec other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } - - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - InlinePresentationSpec that = (InlinePresentationSpec) o; - //noinspection PointlessBooleanExpression - return true - && java.util.Objects.equals(mMinSize, that.mMinSize) - && java.util.Objects.equals(mMaxSize, that.mMaxSize) - && java.util.Objects.equals(mStyle, that.mStyle); - } - - @Override - @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - - int _hash = 1; - _hash = 31 * _hash + java.util.Objects.hashCode(mMinSize); - _hash = 31 * _hash + java.util.Objects.hashCode(mMaxSize); - _hash = 31 * _hash + java.util.Objects.hashCode(mStyle); - return _hash; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - dest.writeSize(mMinSize); - dest.writeSize(mMaxSize); - dest.writeBundle(mStyle); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ InlinePresentationSpec(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - Size minSize = (Size) in.readSize(); - Size maxSize = (Size) in.readSize(); - Bundle style = in.readBundle(); - - this.mMinSize = minSize; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMinSize); - this.mMaxSize = maxSize; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMaxSize); - this.mStyle = style; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mStyle); - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<InlinePresentationSpec> CREATOR - = new Parcelable.Creator<InlinePresentationSpec>() { - @Override - public InlinePresentationSpec[] newArray(int size) { - return new InlinePresentationSpec[size]; - } - - @Override - public InlinePresentationSpec createFromParcel(@NonNull android.os.Parcel in) { - return new InlinePresentationSpec(in); - } - }; - - /** - * A builder for {@link InlinePresentationSpec} - */ - @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member - public static final class Builder extends BaseBuilder { - - private @NonNull Size mMinSize; - private @NonNull Size mMaxSize; - private @NonNull Bundle mStyle; - - private long mBuilderFieldsSet = 0L; - - /** - * Creates a new Builder. - * - * @param minSize - * The minimal size of the suggestion. - * @param maxSize - * The maximal size of the suggestion. - */ - @UnsupportedAppUsage - public Builder( - @NonNull Size minSize, - @NonNull Size maxSize) { - mMinSize = minSize; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMinSize); - mMaxSize = maxSize; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMaxSize); - } - - /** - * The extras encoding the UI style information. Defaults to {@code Bundle.EMPTY} in which case - * the default system UI style will be used. - */ - @DataClass.Generated.Member - public @NonNull Builder setStyle(@NonNull Bundle value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; - mStyle = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - @UnsupportedAppUsage - @NonNull - public InlinePresentationSpec build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x8; // Mark builder used - - if ((mBuilderFieldsSet & 0x4) == 0) { - mStyle = defaultStyle(); - } - InlinePresentationSpec o = new InlinePresentationSpec( - mMinSize, - mMaxSize, - mStyle); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x8) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } - - @DataClass.Generated( - time = 1585691139012L, - codegenVersion = "1.0.15", - sourceFile = "frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java", - inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static android.os.Bundle defaultStyle()\npublic android.widget.inline.InlinePresentationSpec toWidget()\npublic static android.view.inline.InlinePresentationSpec fromWidget(android.widget.inline.InlinePresentationSpec)\npublic static java.util.List<android.view.inline.InlinePresentationSpec> fromWidgets(java.util.List<android.widget.inline.InlinePresentationSpec>)\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java index 3e9ffa7787f6..1c703ecf06ca 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java +++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; -import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcelable; import android.widget.inline.InlinePresentationSpec; @@ -87,17 +86,6 @@ public final class InlineSuggestionInfo implements Parcelable { return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned); } - /** - * The presentation spec to which the inflated suggestion view abides. - * - * @hide - * @removed - */ - @UnsupportedAppUsage - public @NonNull android.view.inline.InlinePresentationSpec getPresentationSpec() { - return android.view.inline.InlinePresentationSpec.fromWidget(mInlinePresentationSpec); - } - // Code below generated by codegen v1.0.15. @@ -358,10 +346,10 @@ public final class InlineSuggestionInfo implements Parcelable { }; @DataClass.Generated( - time = 1585633580662L, + time = 1586992414034L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java", - inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint({\"IntentName\"}) @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inline.InlinePresentationSpec getPresentationSpec()\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint({\"IntentName\"}) @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index af896fca932a..d282b56aedb6 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -19,7 +19,6 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; -import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.os.IBinder; import android.os.LocaleList; @@ -93,20 +92,6 @@ public final class InlineSuggestionsRequest implements Parcelable { private int mHostDisplayId; /** - * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion - * count is larger than the number of specs in the list, then the last spec is used for the - * remainder of the suggestions. The list should not be empty. - * - * @hide - * @removed - */ - @UnsupportedAppUsage - @NonNull - public List<android.view.inline.InlinePresentationSpec> getPresentationSpecs() { - return android.view.inline.InlinePresentationSpec.fromWidgets(mInlinePresentationSpecs); - } - - /** * @hide * @see {@link #mHostInputToken}. */ @@ -170,17 +155,6 @@ public final class InlineSuggestionsRequest implements Parcelable { /** @hide */ abstract static class BaseBuilder { - /** - * @hide - * @removed - */ - @UnsupportedAppUsage - @NonNull - public Builder addPresentationSpecs( - @NonNull android.view.inline.InlinePresentationSpec value) { - return ((Builder) this).addInlinePresentationSpecs(value.toWidget()); - } - abstract Builder setInlinePresentationSpecs( @NonNull List<android.widget.inline.InlinePresentationSpec> specs); @@ -608,10 +582,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1585768018462L, + time = 1586992395497L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 62dd192a6d67..51d37a53f21f 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -5440,6 +5440,9 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent ev) { + if (!mTextView.isFromPrimePointer(ev, true)) { + return true; + } if (mFlagInsertionHandleGesturesEnabled && mFlagCursorDragFromAnywhereEnabled) { // Should only enable touch through when cursor drag is enabled. // Otherwise the insertion handle view cannot be moved. @@ -5908,6 +5911,9 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent event) { + if (!mTextView.isFromPrimePointer(event, true)) { + return true; + } boolean superResult = super.onTouchEvent(event); switch (event.getActionMasked()) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e1783181457f..4be9e1a2051b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -855,6 +855,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int mTextEditSuggestionContainerLayout; int mTextEditSuggestionHighlightStyle; + private static final int NO_POINTER_ID = -1; + /** + * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among + * TextView and the handle views which are rendered on popup windows. + */ + private int mPrimePointerId = NO_POINTER_ID; + + /** + * Whether the prime pointer is from the event delivered to selection handle or insertion + * handle. + */ + private boolean mIsPrimePointerFromHandleView; + /** * {@link EditText} specific data, created on demand when one of the Editor fields is used. * See {@link #createEditorIfNeeded()}. @@ -10886,6 +10899,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** + * Called from onTouchEvent() to prevent the touches by secondary fingers. + * Dragging on handles can revise cursor/selection, so can dragging on the text view. + * This method is a lock to avoid processing multiple fingers on both text view and handles. + * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. + * + * @param event The motion event that is being handled and carries the pointer info. + * @param fromHandleView true if the event is delivered to selection handle or insertion + * handle; false if this event is delivered to TextView. + * @return Returns true to indicate that onTouchEvent() can continue processing the motion + * event, otherwise false. + * - Always returns true for the first finger. + * - For secondary fingers, if the first or current finger is from TextView, returns false. + * This is to make touch mutually exclusive between the TextView and the handles, but + * not among the handles. + */ + boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { + if (mPrimePointerId == NO_POINTER_ID) { + mPrimePointerId = event.getPointerId(0); + mIsPrimePointerFromHandleView = fromHandleView; + } else if (mPrimePointerId != event.getPointerId(0)) { + return mIsPrimePointerFromHandleView && fromHandleView; + } + if (event.getActionMasked() == MotionEvent.ACTION_UP + || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { + mPrimePointerId = -1; + } + return true; + } + @Override public boolean onTouchEvent(MotionEvent event) { if (DEBUG_CURSOR) { @@ -10894,6 +10937,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener MotionEvent.actionToString(event.getActionMasked()), event.getX(), event.getY()); } + if (!isFromPrimePointer(event, false)) { + return true; + } final int action = event.getActionMasked(); if (mEditor != null) { diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index d64b5f1118dc..be66d0c238cc 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -232,9 +232,8 @@ public class AccessibilityShortcutController { } /** - * Show toast if current assigned shortcut target is an accessibility service and its target - * sdk version is less than or equal to Q, or greater than Q and does not request - * accessibility button. + * Show toast to alert the user that the accessibility shortcut turned on or off an + * accessibility service. */ private void showToast() { final AccessibilityServiceInfo serviceInfo = getInfoForTargetService(); @@ -247,12 +246,15 @@ public class AccessibilityShortcutController { } final boolean requestA11yButton = (serviceInfo.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; - if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo - .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton) { + final boolean isServiceEnabled = isServiceEnabled(serviceInfo); + if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion + > Build.VERSION_CODES.Q && requestA11yButton && isServiceEnabled) { + // An accessibility button callback is sent to the target accessibility service. + // No need to show up a toast in this case. return; } // For accessibility services, show a toast explaining what we're doing. - String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo) + String toastMessageFormatString = mContext.getString(isServiceEnabled ? R.string.accessibility_shortcut_disabling_service : R.string.accessibility_shortcut_enabling_service); String toastMessage = String.format(toastMessageFormatString, serviceName); diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index d43333e507a6..b1e356d258ee 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -300,30 +300,26 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) { - UserHandle listUserHandle = activeListAdapter.getUserHandle(); - - if (UserHandle.myUserId() != listUserHandle.getIdentifier()) { - if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(), - UserHandle.myUserId(), listUserHandle.getIdentifier())) { - if (listUserHandle.equals(mPersonalProfileUserHandle)) { - DevicePolicyEventLogger.createEvent( - DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL) - .setStrings(getMetricsCategory()) - .write(); - showNoWorkToPersonalIntentsEmptyState(activeListAdapter); - } else { - DevicePolicyEventLogger.createEvent( - DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK) - .setStrings(getMetricsCategory()) - .write(); - showNoPersonalToWorkIntentsEmptyState(activeListAdapter); - } - return false; - } + if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) { + activeListAdapter.postListReadyRunnable(doPostProcessing); + return false; } return activeListAdapter.rebuildList(doPostProcessing); } + private boolean shouldShowNoCrossProfileIntentsEmptyState( + ResolverListAdapter activeListAdapter) { + UserHandle listUserHandle = activeListAdapter.getUserHandle(); + return UserHandle.myUserId() != listUserHandle.getIdentifier() + && allowShowNoCrossProfileIntentsEmptyState() + && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(), + UserHandle.myUserId(), listUserHandle.getIdentifier()); + } + + boolean allowShowNoCrossProfileIntentsEmptyState() { + return true; + } + protected abstract void showWorkProfileOffEmptyState( ResolverListAdapter activeListAdapter, View.OnClickListener listener); @@ -353,12 +349,35 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * anyway. */ void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) { + if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) { + return; + } if (maybeShowWorkProfileOffEmptyState(listAdapter)) { return; } maybeShowNoAppsAvailableEmptyState(listAdapter); } + private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) { + if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) { + return false; + } + if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) { + DevicePolicyEventLogger.createEvent( + DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL) + .setStrings(getMetricsCategory()) + .write(); + showNoWorkToPersonalIntentsEmptyState(listAdapter); + } else { + DevicePolicyEventLogger.createEvent( + DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK) + .setStrings(getMetricsCategory()) + .write(); + showNoPersonalToWorkIntentsEmptyState(listAdapter); + } + return true; + } + /** * Returns {@code true} if the work profile off empty state screen is shown. */ @@ -429,16 +448,16 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { subtitle.setVisibility(View.GONE); } - ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon); Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button); + button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE); + button.setOnClickListener(buttonOnClick); + + ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon); if (!getContext().getResources().getBoolean(R.bool.resolver_landscape_phone)) { icon.setVisibility(View.VISIBLE); icon.setImageResource(iconRes); - button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE); - button.setOnClickListener(buttonOnClick); } else { icon.setVisibility(View.GONE); - button.setVisibility(View.GONE); } activeListAdapter.markTabLoaded(); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 3e7f24b034ac..d851a099d0e1 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2171,7 +2171,7 @@ public class ChooserActivity extends ResolverActivity implements mChooserMultiProfilePagerAdapter.getActiveListAdapter(); if (currentListAdapter != null) { currentListAdapter.updateModel(info.getResolvedComponentName()); - currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(), + currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, targetIntent.getAction()); } if (DEBUG) { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index dd3a6603f46a..1bc982cdb42b 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -179,6 +179,7 @@ public class ResolverActivity extends Activity implements public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device"; private BroadcastReceiver mWorkProfileStateReceiver; + private boolean mIsHeaderCreated; /** * Get the string resource to be used as a label for the link to the resolver activity for an @@ -479,13 +480,42 @@ public class ResolverActivity extends Activity implements == workProfileUserHandle.getIdentifier()), mUseLayoutForBrowsables, /* userHandle */ workProfileUserHandle); + // In the edge case when we have 0 apps in the current profile and >1 apps in the other, + // the intent resolver is started in the other profile. Since this is the only case when + // this happens, we check for it here and set the current profile's tab. + int selectedProfile = getCurrentProfile(); + UserHandle intentUser = UserHandle.of(getLaunchingUserId()); + if (!getUser().equals(intentUser)) { + if (getPersonalProfileUserHandle().equals(intentUser)) { + selectedProfile = PROFILE_PERSONAL; + } else if (getWorkProfileUserHandle().equals(intentUser)) { + selectedProfile = PROFILE_WORK; + } + } return new ResolverMultiProfilePagerAdapter( /* context */ this, personalAdapter, workAdapter, - /* defaultProfile */ getCurrentProfile(), + selectedProfile, getPersonalProfileUserHandle(), - getWorkProfileUserHandle()); + getWorkProfileUserHandle(), + /* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser)); + } + + /** + * Returns the user id of the user that the starting intent originated from. + * <p>This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()}, + * as there are edge cases when the intent resolver is launched in the other profile. + * For example, when we have 0 resolved apps in current profile and multiple resolved apps + * in the other profile, opening a link from the current profile launches the intent resolver + * in the other one. b/148536209 for more info. + */ + private int getLaunchingUserId() { + int contentUserHint = getIntent().getContentUserHint(); + if (contentUserHint == UserHandle.USER_CURRENT) { + return UserHandle.myUserId(); + } + return contentUserHint; } protected @Profile int getCurrentProfile() { @@ -856,7 +886,7 @@ public class ResolverActivity extends Activity implements private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, boolean filtered) { - if (mMultiProfilePagerAdapter.getCurrentUserHandle() != getUser()) { + if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) { // Never allow the inactive profile to always open an app. mAlwaysButton.setEnabled(false); return; @@ -995,10 +1025,7 @@ public class ResolverActivity extends Activity implements mMultiProfilePagerAdapter.showListView(listAdapter); } if (doPostProcessing) { - if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() - == UserHandle.myUserId()) { - setHeader(); - } + maybeCreateHeader(listAdapter); resetButtonBar(); onListRebuilt(listAdapter); } @@ -1679,10 +1706,15 @@ public class ResolverActivity extends Activity implements /** * Configure the area above the app selection list (title, content preview, etc). + * <p>The header is created once when first launching the activity and whenever a package is + * installed or uninstalled. */ - public void setHeader() { - if (mMultiProfilePagerAdapter.getActiveListAdapter().getCount() == 0 - && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) { + private void maybeCreateHeader(ResolverListAdapter listAdapter) { + if (mIsHeaderCreated) { + return; + } + if (!shouldShowTabs() + && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) { final TextView titleView = findViewById(R.id.title); if (titleView != null) { titleView.setVisibility(View.GONE); @@ -1703,8 +1735,9 @@ public class ResolverActivity extends Activity implements final ImageView iconView = findViewById(R.id.icon); if (iconView != null) { - mMultiProfilePagerAdapter.getActiveListAdapter().loadFilteredItemIconTaskAsync(iconView); + listAdapter.loadFilteredItemIconTaskAsync(iconView); } + mIsHeaderCreated = true; } protected void resetButtonBar() { @@ -1804,6 +1837,7 @@ public class ResolverActivity extends Activity implements // turning on. return; } + mIsHeaderCreated = false; boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true); if (listRebuilt) { ResolverListAdapter activeListAdapter = diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 579abeecad13..73109c5c1fbc 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -165,8 +165,9 @@ public class ResolverListAdapter extends BaseAdapter { mResolverListController.updateModel(componentName); } - public void updateChooserCounts(String packageName, int userId, String action) { - mResolverListController.updateChooserCounts(packageName, userId, action); + public void updateChooserCounts(String packageName, String action) { + mResolverListController.updateChooserCounts( + packageName, getUserHandle().getIdentifier(), action); } List<ResolvedComponentInfo> getUnfilteredResolveList() { diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index b690a18f2d0e..ad31d8b2e49a 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -35,6 +35,7 @@ import com.android.internal.widget.PagerAdapter; public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter { private final ResolverProfileDescriptor[] mItems; + private final boolean mShouldShowNoCrossProfileIntentsEmptyState; ResolverMultiProfilePagerAdapter(Context context, ResolverListAdapter adapter, @@ -44,6 +45,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA mItems = new ResolverProfileDescriptor[] { createProfileDescriptor(adapter) }; + mShouldShowNoCrossProfileIntentsEmptyState = true; } ResolverMultiProfilePagerAdapter(Context context, @@ -51,13 +53,15 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA ResolverListAdapter workAdapter, @Profile int defaultProfile, UserHandle personalProfileUserHandle, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + boolean shouldShowNoCrossProfileIntentsEmptyState) { super(context, /* currentPage */ defaultProfile, personalProfileUserHandle, workProfileUserHandle); mItems = new ResolverProfileDescriptor[] { createProfileDescriptor(personalAdapter), createProfileDescriptor(workAdapter) }; + mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState; } private ResolverProfileDescriptor createProfileDescriptor( @@ -163,6 +167,11 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA } @Override + boolean allowShowNoCrossProfileIntentsEmptyState() { + return mShouldShowNoCrossProfileIntentsEmptyState; + } + + @Override protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter, View.OnClickListener listener) { showEmptyState(activeListAdapter, diff --git a/core/java/com/android/internal/app/SystemUserHomeActivity.java b/core/java/com/android/internal/app/SystemUserHomeActivity.java index 26fbf6f826a8..ee936a3f9abc 100644 --- a/core/java/com/android/internal/app/SystemUserHomeActivity.java +++ b/core/java/com/android/internal/app/SystemUserHomeActivity.java @@ -17,10 +17,27 @@ package com.android.internal.app; import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.R; /** * Placeholder home activity, which is always installed on the system user. At least one home * activity must be present and enabled in order for the system to boot. */ public class SystemUserHomeActivity extends Activity { + private static final String TAG = "SystemUserHome"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.i(TAG, "onCreate"); + setContentView(R.layout.system_user_home); + } + + protected void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + } } diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java index eda04a6a322a..9bdbdc70d74d 100644 --- a/core/java/com/android/internal/app/procstats/DumpUtils.java +++ b/core/java/com/android/internal/app/procstats/DumpUtils.java @@ -16,14 +16,39 @@ package com.android.internal.app.procstats; +import static com.android.internal.app.procstats.ProcessStats.ADJ_COUNT; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_COUNT; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE; +import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; +import static com.android.internal.app.procstats.ProcessStats.ADJ_NOTHING; +import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_MOD; +import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_OFF; +import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_ON; +import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP; +import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY; +import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT; +import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY; +import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT; +import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT; +import static com.android.internal.app.procstats.ProcessStats.STATE_HOME; +import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND; +import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_FOREGROUND; +import static com.android.internal.app.procstats.ProcessStats.STATE_LAST_ACTIVITY; +import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING; +import static com.android.internal.app.procstats.ProcessStats.STATE_PERSISTENT; +import static com.android.internal.app.procstats.ProcessStats.STATE_RECEIVER; +import static com.android.internal.app.procstats.ProcessStats.STATE_SERVICE; +import static com.android.internal.app.procstats.ProcessStats.STATE_SERVICE_RESTARTING; +import static com.android.internal.app.procstats.ProcessStats.STATE_TOP; + import android.os.UserHandle; import android.service.procstats.ProcessStatsEnums; import android.service.procstats.ProcessStatsStateProto; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; -import static com.android.internal.app.procstats.ProcessStats.*; - import java.io.PrintWriter; import java.util.ArrayList; @@ -38,6 +63,7 @@ public final class DumpUtils { public static final String[] STATE_NAMES_CSV; static final String[] STATE_TAGS; static final int[] STATE_PROTO_ENUMS; + private static final int[] PROCESS_STATS_STATE_TO_AGGREGATED_STATE; // Make the mapping easy to update. static { @@ -126,6 +152,39 @@ public final class DumpUtils { STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsEnums.PROCESS_STATE_CACHED_EMPTY; + + // Remap states, as defined by ProcessStats.java, to a reduced subset of states for data + // aggregation / size reduction purposes. + PROCESS_STATS_STATE_TO_AGGREGATED_STATE = new int[STATE_COUNT]; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_PERSISTENT] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_PERSISTENT; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_TOP] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_TOP; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_IMPORTANT_FOREGROUND] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_IMPORTANT_FOREGROUND; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_IMPORTANT_BACKGROUND] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BACKGROUND; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BACKUP] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BACKGROUND; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_SERVICE] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BACKGROUND; + // "Restarting" is not a real state, so this shouldn't exist. + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_SERVICE_RESTARTING] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_UNKNOWN; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_RECEIVER] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_RECEIVER; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_HEAVY_WEIGHT] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BACKGROUND; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_HOME] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_LAST_ACTIVITY] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY_CLIENT] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED; + PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_EMPTY] = + ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED; } public static final String[] ADJ_SCREEN_NAMES_CSV = new String[] { @@ -455,4 +514,51 @@ public final class DumpUtils { } return itemName; } + + /** + * Aggregate process states to reduce size of statistics logs. + * + * <p>Involves unpacking the three parts of state (process state / device memory state / + * screen state), manipulating the elements, then re-packing the new values into a single + * int. This integer is guaranteed to be unique for any given combination of state elements. + * + * @param curState current state as used in mCurState in {@class ProcessState} ie. a value + * combined from the process's state, the device's memory pressure state, and + * the device's screen on/off state. + * @return an integer representing the combination of screen state and process state, where + * process state has been aggregated. + */ + public static int aggregateCurrentProcessState(int curState) { + int screenStateIndex = curState / (ADJ_SCREEN_MOD * STATE_COUNT); + // extract process state from the compound state variable (discarding memory state) + int procStateIndex = curState % STATE_COUNT; + + // Remap process state per array above. + try { + procStateIndex = PROCESS_STATS_STATE_TO_AGGREGATED_STATE[procStateIndex]; + } catch (IndexOutOfBoundsException e) { + procStateIndex = ProcessStatsEnums.AGGREGATED_PROCESS_STATE_UNKNOWN; + } + + // Pack screen & process state using bit shifting + return (procStateIndex << 0xf) | screenStateIndex; + } + + /** Print aggregated tags generated via {@code #aggregateCurrentProcessState}. */ + public static void printAggregatedProcStateTagProto(ProtoOutputStream proto, long screenId, + long stateId, int state) { + // screen state is in lowest 0xf bits, process state is in next 0xf bits up + + try { + proto.write(stateId, STATE_PROTO_ENUMS[state >> 0xf]); + } catch (IndexOutOfBoundsException e) { + proto.write(stateId, ProcessStatsEnums.PROCESS_STATE_UNKNOWN); + } + + try { + proto.write(screenId, ADJ_SCREEN_PROTO_ENUMS[state & 0xf]); + } catch (IndexOutOfBoundsException e) { + proto.write(screenId, ProcessStatsEnums.SCREEN_STATE_UNKNOWN); + } + } } diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index a6bed5bdfedc..79ff5948f32b 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -16,22 +16,6 @@ package com.android.internal.app.procstats; -import android.os.Parcel; -import android.os.SystemClock; -import android.os.UserHandle; -import android.service.procstats.ProcessStatsProto; -import android.service.procstats.ProcessStatsStateProto; -import android.util.ArrayMap; -import android.util.DebugUtils; -import android.util.Log; -import android.util.LongSparseArray; -import android.util.Slog; -import android.util.SparseLongArray; -import android.util.TimeUtils; -import android.util.proto.ProtoOutputStream; -import android.util.proto.ProtoUtils; - - import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_COUNT; import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM; @@ -60,6 +44,21 @@ import static com.android.internal.app.procstats.ProcessStats.STATE_SERVICE; import static com.android.internal.app.procstats.ProcessStats.STATE_SERVICE_RESTARTING; import static com.android.internal.app.procstats.ProcessStats.STATE_TOP; +import android.os.Parcel; +import android.os.SystemClock; +import android.os.UserHandle; +import android.service.procstats.ProcessStatsProto; +import android.service.procstats.ProcessStatsStateProto; +import android.util.ArrayMap; +import android.util.DebugUtils; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; +import android.util.SparseLongArray; +import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; + import com.android.internal.app.procstats.ProcessStats.PackageState; import com.android.internal.app.procstats.ProcessStats.ProcessStateHolder; import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection; @@ -1418,4 +1417,109 @@ public final class ProcessState { proto.end(token); } + + /** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */ + public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId, + String procName, int uid, long now) { + // Group proc stats by aggregated type (only screen state + process state) + SparseLongArray durationByState = new SparseLongArray(); + boolean didCurState = false; + for (int i = 0; i < mDurations.getKeyCount(); i++) { + final int key = mDurations.getKeyAt(i); + final int type = SparseMappingTable.getIdFromKey(key); + final int aggregatedType = DumpUtils.aggregateCurrentProcessState(type); + + long time = mDurations.getValue(key); + if (mCurCombinedState == type) { + didCurState = true; + time += now - mStartTime; + } + int index = durationByState.indexOfKey(aggregatedType); + if (index >= 0) { + durationByState.put(aggregatedType, time + durationByState.valueAt(index)); + } else { + durationByState.put(aggregatedType, time); + } + } + if (!didCurState && mCurCombinedState != STATE_NOTHING) { + final int aggregatedType = DumpUtils.aggregateCurrentProcessState(mCurCombinedState); + int index = durationByState.indexOfKey(aggregatedType); + if (index >= 0) { + durationByState.put(aggregatedType, + (now - mStartTime) + durationByState.valueAt(index)); + } else { + durationByState.put(aggregatedType, now - mStartTime); + } + } + + // Now we have total durations, aggregate the RSS values + SparseLongArray meanRssByState = new SparseLongArray(); + SparseLongArray maxRssByState = new SparseLongArray(); + // compute weighted averages and max-of-max + for (int i = 0; i < mPssTable.getKeyCount(); i++) { + final int key = mPssTable.getKeyAt(i); + final int type = SparseMappingTable.getIdFromKey(key); + if (durationByState.indexOfKey(type) < 0) { + // state without duration should not have stats! + continue; + } + final int aggregatedType = DumpUtils.aggregateCurrentProcessState(type); + + long[] rssMeanAndMax = mPssTable.getRssMeanAndMax(key); + + // compute mean * duration, then store sum of that in meanRssByState + long meanTimesDuration = rssMeanAndMax[0] * mDurations.getValue(key); + if (meanRssByState.indexOfKey(aggregatedType) >= 0) { + meanRssByState.put(aggregatedType, + meanTimesDuration + meanRssByState.get(aggregatedType)); + } else { + meanRssByState.put(aggregatedType, meanTimesDuration); + } + + // accumulate max-of-maxes in maxRssByState + if (maxRssByState.indexOfKey(aggregatedType) >= 0 + && maxRssByState.get(aggregatedType) < rssMeanAndMax[1]) { + maxRssByState.put(aggregatedType, rssMeanAndMax[1]); + } else if (maxRssByState.indexOfKey(aggregatedType) < 0) { + maxRssByState.put(aggregatedType, rssMeanAndMax[1]); + } + } + + // divide the means by the durations to get the weighted mean-of-means + for (int i = 0; i < durationByState.size(); i++) { + int aggregatedKey = durationByState.keyAt(i); + if (meanRssByState.indexOfKey(aggregatedKey) < 0) { + // these data structures should be consistent + continue; + } + meanRssByState.put(aggregatedKey, + meanRssByState.get(aggregatedKey) / durationByState.get(aggregatedKey)); + } + + // build the output + final long token = proto.start(fieldId); + proto.write(ProcessStatsProto.PROCESS, procName); + proto.write(ProcessStatsProto.UID, uid); + + for (int i = 0; i < durationByState.size(); i++) { + final long stateToken = proto.start(ProcessStatsProto.STATES); + + final int aggregatedKey = durationByState.keyAt(i); + + DumpUtils.printAggregatedProcStateTagProto(proto, + ProcessStatsStateProto.SCREEN_STATE, + ProcessStatsStateProto.PROCESS_STATE, + aggregatedKey); + proto.write(ProcessStatsStateProto.DURATION_MS, durationByState.get(aggregatedKey)); + + ProtoUtils.toAggStatsProto(proto, ProcessStatsStateProto.RSS, + 0, /* do not output a minimum value */ + meanRssByState.get(aggregatedKey), + maxRssByState.get(aggregatedKey)); + + proto.end(stateToken); + } + + proto.end(token); + } } diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 009b1e0fa829..80f6272794d1 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -2178,29 +2178,7 @@ public final class ProcessStats implements Parcelable { * Writes to ProtoOutputStream. */ public void dumpDebug(ProtoOutputStream proto, long now, int section) { - proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime); - proto.write(ProcessStatsSectionProto.END_REALTIME_MS, - mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime); - proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime); - proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime); - proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime); - proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss); - boolean partial = true; - if ((mFlags & FLAG_SHUTDOWN) != 0) { - proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN); - partial = false; - } - if ((mFlags & FLAG_SYSPROPS) != 0) { - proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS); - partial = false; - } - if ((mFlags & FLAG_COMPLETE) != 0) { - proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE); - partial = false; - } - if (partial) { - proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL); - } + dumpProtoPreamble(proto); final int NPAGETYPES = mPageTypeLabels.size(); for (int i = 0; i < NPAGETYPES; i++) { @@ -2247,6 +2225,49 @@ public final class ProcessStats implements Parcelable { } } + /** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */ + public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto) { + dumpProtoPreamble(proto); + final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); + for (int ip = 0; ip < procMap.size(); ip++) { + final String procName = procMap.keyAt(ip); + final SparseArray<ProcessState> uids = procMap.valueAt(ip); + for (int iu = 0; iu < uids.size(); iu++) { + final int uid = uids.keyAt(iu); + final ProcessState procState = uids.valueAt(iu); + procState.dumpAggregatedProtoForStatsd(proto, + ProcessStatsSectionProto.PROCESS_STATS, + procName, uid, mTimePeriodEndRealtime); + } + } + } + + private void dumpProtoPreamble(ProtoOutputStream proto) { + proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime); + proto.write(ProcessStatsSectionProto.END_REALTIME_MS, + mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime); + proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime); + proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime); + proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime); + proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss); + boolean partial = true; + if ((mFlags & FLAG_SHUTDOWN) != 0) { + proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN); + partial = false; + } + if ((mFlags & FLAG_SYSPROPS) != 0) { + proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS); + partial = false; + } + if ((mFlags & FLAG_COMPLETE) != 0) { + proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE); + partial = false; + } + if (partial) { + proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL); + } + } + final public static class ProcessStateHolder { public final long appVersion; public ProcessState state; diff --git a/core/java/com/android/internal/app/procstats/PssTable.java b/core/java/com/android/internal/app/procstats/PssTable.java index fc93c3a0094e..a6bae6e05dbc 100644 --- a/core/java/com/android/internal/app/procstats/PssTable.java +++ b/core/java/com/android/internal/app/procstats/PssTable.java @@ -16,17 +16,17 @@ package com.android.internal.app.procstats; +import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE; +import static com.android.internal.app.procstats.ProcessStats.PSS_COUNT; +import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM; +import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MAXIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_SAMPLE_COUNT; -import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM; -import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE; -import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM; -import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM; -import static com.android.internal.app.procstats.ProcessStats.PSS_COUNT; +import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM; import android.service.procstats.ProcessStatsStateProto; import android.util.proto.ProtoOutputStream; @@ -133,7 +133,6 @@ public class PssTable extends SparseMappingTable.Table { } if (stats[statsIndex + PSS_RSS_MINIMUM] > minRss) { - stats[statsIndex + PSS_RSS_MINIMUM] = minRss; } stats[statsIndex + PSS_RSS_AVERAGE] = (long)(((stats[statsIndex + PSS_RSS_AVERAGE] @@ -167,4 +166,10 @@ public class PssTable extends SparseMappingTable.Table { stats[statsIndex + PSS_RSS_AVERAGE], stats[statsIndex + PSS_RSS_MAXIMUM]); } + + long[] getRssMeanAndMax(int key) { + final long[] stats = getArrayForKey(key); + final int statsIndex = SparseMappingTable.getIndexFromKey(key); + return new long[]{stats[statsIndex + PSS_RSS_AVERAGE], stats[statsIndex + PSS_RSS_MAXIMUM]}; + } } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index bd1bae6d83fb..b678d627d48c 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -523,6 +523,7 @@ message StateControllerProto { optional bool is_idle = 1; optional bool is_screen_on = 2; optional bool is_dock_idle = 3; + optional bool in_car_mode = 4; } oneof active_tracker { diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 60892557891b..55ea3159c44c 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -250,7 +250,7 @@ message TaskProto { reserved 3; // activity optional bool fills_parent = 4; optional .android.graphics.RectProto bounds = 5; - optional .android.graphics.RectProto displayed_bounds = 6; + optional .android.graphics.RectProto displayed_bounds = 6 [deprecated=true]; optional bool defer_removal = 7; optional int32 surface_width = 8; optional int32 surface_height = 9; diff --git a/core/proto/android/service/procstats_enum.proto b/core/proto/android/service/procstats_enum.proto index cc3fe5af775b..2abf3730aa9f 100644 --- a/core/proto/android/service/procstats_enum.proto +++ b/core/proto/android/service/procstats_enum.proto @@ -76,3 +76,27 @@ enum ServiceOperationState { SERVICE_OPERATION_STATE_BOUND = 4; SERVICE_OPERATION_STATE_EXECUTING = 5; } + +// this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java +// and not frameworks/base/core/java/android/app/ActivityManager.java +enum AggregatedProcessState { + AGGREGATED_PROCESS_STATE_UNKNOWN = 0; + // Persistent system process; PERSISTENT or PERSISTENT_UI in ActivityManager + AGGREGATED_PROCESS_STATE_PERSISTENT = 1; + // Top activity; actually any visible activity; TOP or TOP_SLEEPING in ActivityManager + AGGREGATED_PROCESS_STATE_TOP = 2; + // Bound top foreground process; BOUND_TOP or BOUND_FOREGROUND_SERVICE in ActivityManager + AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS = 3; + // Important foreground process; FOREGROUND_SERVICE in ActivityManager + AGGREGATED_PROCESS_STATE_FGS = 4; + // Important foreground process ; IMPORTANT_FOREGROUND in ActivityManager + AGGREGATED_PROCESS_STATE_IMPORTANT_FOREGROUND = 5; + // Various background processes; IMPORTANT_BACKGROUND, TRANSIENT_BACKGROUND, BACKUP, SERVICE, + // HEAVY_WEIGHT in ActivityManager + AGGREGATED_PROCESS_STATE_BACKGROUND = 6; + // Process running a receiver; RECEIVER in ActivityManager + AGGREGATED_PROCESS_STATE_RECEIVER = 7; + // Various cached processes; HOME, LAST_ACTIVITY, CACHED_ACTIVITY, CACHED_RECENT, + // CACHED_ACTIVITY_CLIENT, CACHED_EMPTY in ActivityManager + AGGREGATED_PROCESS_STATE_CACHED = 8; +}
\ No newline at end of file diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 6f36aae8a1d4..ece59e2abb22 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -143,34 +143,35 @@ android:layout_height="match_parent" android:layout_width="wrap_content" android:layout_marginStart="6dp" - android:orientation="horizontal" > - <ImageButton + android:background="?android:selectableItemBackgroundBorderless" + android:orientation="horizontal"> + <ImageView android:id="@+id/camera" android:layout_width="?attr/notificationHeaderIconSize" android:layout_height="?attr/notificationHeaderIconSize" android:src="@drawable/ic_camera" - android:background="?android:selectableItemBackgroundBorderless" android:visibility="gone" + android:focusable="false" android:contentDescription="@string/notification_appops_camera_active" /> - <ImageButton + <ImageView android:id="@+id/mic" android:layout_width="?attr/notificationHeaderIconSize" android:layout_height="?attr/notificationHeaderIconSize" android:src="@drawable/ic_mic" - android:background="?android:selectableItemBackgroundBorderless" android:layout_marginStart="4dp" android:visibility="gone" + android:focusable="false" android:contentDescription="@string/notification_appops_microphone_active" /> - <ImageButton + <ImageView android:id="@+id/overlay" android:layout_width="?attr/notificationHeaderIconSize" android:layout_height="?attr/notificationHeaderIconSize" android:src="@drawable/ic_alert_window_layer" - android:background="?android:selectableItemBackgroundBorderless" android:layout_marginStart="4dp" android:visibility="gone" + android:focusable="false" android:contentDescription="@string/notification_appops_overlay_active" /> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index e986b1886abf..8a57e057b084 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -195,35 +195,36 @@ android:layout_width="wrap_content" android:paddingTop="3dp" android:layout_marginStart="2dp" + android:background="?android:selectableItemBackgroundBorderless" android:orientation="horizontal" > - <ImageButton + <ImageView android:layout_marginStart="4dp" android:id="@+id/camera" android:layout_width="?attr/notificationHeaderIconSize" android:layout_height="?attr/notificationHeaderIconSize" android:src="@drawable/ic_camera" - android:background="?android:selectableItemBackgroundBorderless" android:visibility="gone" + android:focusable="false" android:contentDescription="@string/notification_appops_camera_active" /> - <ImageButton + <ImageView android:id="@+id/mic" android:layout_width="?attr/notificationHeaderIconSize" android:layout_height="?attr/notificationHeaderIconSize" android:src="@drawable/ic_mic" - android:background="?android:selectableItemBackgroundBorderless" android:layout_marginStart="4dp" android:visibility="gone" + android:focusable="false" android:contentDescription="@string/notification_appops_microphone_active" /> - <ImageButton + <ImageView android:id="@+id/overlay" android:layout_width="?attr/notificationHeaderIconSize" android:layout_height="?attr/notificationHeaderIconSize" android:src="@drawable/ic_alert_window_layer" - android:background="?android:selectableItemBackgroundBorderless" android:layout_marginStart="4dp" android:visibility="gone" + android:focusable="false" android:contentDescription="@string/notification_appops_overlay_active" /> </LinearLayout> diff --git a/core/res/res/layout/system_user_home.xml b/core/res/res/layout/system_user_home.xml new file mode 100644 index 000000000000..8afa42338e36 --- /dev/null +++ b/core/res/res/layout/system_user_home.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#80000000" + android:forceHasOverlappingRendering="false"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_gravity="center" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="20sp" + android:textColor="?android:attr/textColorPrimary" + android:text="Framework Fallback Home"/> + <ProgressBar + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12.75dp" + android:colorControlActivated="?android:attr/textColorPrimary" + android:indeterminate="true"/> + </LinearLayout> +</FrameLayout> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fba237936f6e..8e99356a8a80 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3447,8 +3447,9 @@ TODO: move to input HAL once ready. --> <string name="config_doubleTouchGestureEnableFile"></string> - <!-- Package of the unbundled tv remote service which can connect to tv - remote provider --> + <!-- Comma-separated list of unbundled packages which can connect to the + tv remote provider. The tv remote service is an example of such a + service. --> <string name="config_tvRemoteServicePackage" translatable="false"></string> <!-- True if the device supports persisting security logs across reboots. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 01d4e8c56249..717f326d0378 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1468,6 +1468,7 @@ <java-symbol type="layout" name="select_dialog" /> <java-symbol type="layout" name="simple_dropdown_hint" /> <java-symbol type="layout" name="status_bar_latest_event_content" /> + <java-symbol type="layout" name="system_user_home" /> <java-symbol type="layout" name="text_edit_action_popup_text" /> <java-symbol type="layout" name="text_drag_thumbnail" /> <java-symbol type="layout" name="typing_filter" /> diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index a9f251e5c3a7..89cc6e743752 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -528,6 +528,47 @@ public class EditorCursorDragTest { } @Test + public void testCursorDrag_multiTouch() throws Throwable { + String text = "line1: This is the 1st line: A"; + onView(withId(R.id.textview)).perform(replaceText(text)); + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + final int startIndex = text.indexOf("1st line"); + Layout layout = tv.getLayout(); + final float cursorStartX = + layout.getPrimaryHorizontal(startIndex) + tv.getTotalPaddingLeft(); + final float cursorStartY = layout.getLineTop(1) + tv.getTotalPaddingTop(); + + // Taps to show the insertion handle. + tapAtPoint(tv, cursorStartX, cursorStartY); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex)); + View handleView = editor.getInsertionController().getHandle(); + + // Taps & holds the insertion handle. + long handleDownTime = sTicker.addAndGet(10_000); + long eventTime = handleDownTime; + dispatchTouchEvent(handleView, downEvent(handleView, handleDownTime, eventTime++, 0, 0)); + + // Tries to Drag the cursor, with the pointer id > 0 (meaning the 2nd finger). + long cursorDownTime = eventTime++; + dispatchTouchEvent(tv, obtainTouchEventWithPointerId( + tv, MotionEvent.ACTION_DOWN, cursorDownTime, eventTime++, 1, + cursorStartX - 50, cursorStartY)); + dispatchTouchEvent(tv, obtainTouchEventWithPointerId( + tv, MotionEvent.ACTION_MOVE, cursorDownTime, eventTime++, 1, + cursorStartX - 100, cursorStartY)); + dispatchTouchEvent(tv, obtainTouchEventWithPointerId( + tv, MotionEvent.ACTION_UP, cursorDownTime, eventTime++, 1, + cursorStartX - 100, cursorStartY)); + + // Checks the cursor drag doesn't work while the handle is being hold. + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex)); + + // Finger up on the insertion handle. + dispatchTouchEvent(handleView, upEvent(handleView, handleDownTime, eventTime, 0, 0)); + } + + @Test public void testCursorDrag_snapDistance() throws Throwable { String text = "line1: This is the 1st line: A\n" + "line2: This is the 2nd line: B\n" @@ -626,6 +667,24 @@ public class EditorCursorDragTest { return event; } + private MotionEvent obtainTouchEventWithPointerId( + View view, int action, long downTime, long eventTime, int pointerId, float x, float y) { + Rect r = new Rect(); + view.getBoundsOnScreen(r); + float rawX = x + r.left; + float rawY = y + r.top; + MotionEvent.PointerCoords coordinates = new MotionEvent.PointerCoords(); + coordinates.x = rawX; + coordinates.y = rawY; + MotionEvent event = MotionEvent.obtain( + downTime, eventTime, action, 1, new int[] {pointerId}, + new MotionEvent.PointerCoords[] {coordinates}, + 0, 1f, 1f, 0, 0, 0, 0); + view.toLocalMotionEvent(event); + mMotionEvents.add(event); + return event; + } + private MotionEvent obtainMouseEvent( View view, int action, long downTime, long eventTime, float x, float y) { MotionEvent event = obtainTouchEvent(view, action, downTime, eventTime, x, y); diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 4a33da680585..b21504c73772 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -462,6 +462,7 @@ public class AccessibilityShortcutControllerTest { configureValidShortcutService(); configureApplicationTargetSdkVersion(Build.VERSION_CODES.R); configureRequestAccessibilityButton(); + configureEnabledService(); Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); getController().performAccessibilityShortcut(); @@ -610,6 +611,11 @@ public class AccessibilityShortcutControllerTest { }).when(mHandler).sendMessageAtTime(any(), anyLong()); } + private void configureEnabledService() throws Exception { + when(mAccessibilityManagerService.getEnabledAccessibilityServiceList(anyInt(), anyInt())) + .thenReturn(Collections.singletonList(mServiceInfo)); + } + private AccessibilityShortcutController getController() { AccessibilityShortcutController accessibilityShortcutController = new AccessibilityShortcutController(mContext, mHandler, 0); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index eb39d58019d9..07aa654cae1e 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -38,13 +38,16 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import static org.testng.Assert.assertFalse; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.UserHandle; import android.text.TextUtils; import android.view.View; import android.widget.RelativeLayout; +import android.widget.TextView; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.Espresso; @@ -543,6 +546,51 @@ public class ResolverActivityTest { assertThat(activity.getWorkListAdapter().getCount(), is(4)); } + @Test + public void testWorkTab_headerIsVisibleInPersonalTab() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(1); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createOpenWebsiteIntent(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + TextView headerText = activity.findViewById(R.id.title); + String initialText = headerText.getText().toString(); + assertFalse(initialText.isEmpty(), "Header text is empty."); + assertThat(headerText.getVisibility(), is(View.VISIBLE)); + } + + @Test + public void testWorkTab_switchTabs_headerStaysSame() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(1); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createOpenWebsiteIntent(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + TextView headerText = activity.findViewById(R.id.title); + String initialText = headerText.getText().toString(); + onView(withText(R.string.resolver_work_tab)) + .perform(click()); + + waitForIdle(); + String currentText = headerText.getText().toString(); + assertThat(headerText.getVisibility(), is(View.VISIBLE)); + assertThat(String.format("Header text is not the same when switching tabs, personal profile" + + " header was %s but work profile header is %s", initialText, currentText), + TextUtils.equals(initialText, currentText)); + } + @Ignore // b/148156663 @Test public void testWorkTab_noPersonalApps_canStartWorkApps() @@ -761,6 +809,13 @@ public class ResolverActivityTest { return sendIntent; } + private Intent createOpenWebsiteIntent() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_VIEW); + sendIntent.setData(Uri.parse("https://google.com")); + return sendIntent; + } + private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) { List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { diff --git a/core/tests/overlaytests/host/TEST_MAPPING b/core/tests/overlaytests/host/TEST_MAPPING new file mode 100644 index 000000000000..e0c03e0f2aa4 --- /dev/null +++ b/core/tests/overlaytests/host/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name" : "OverlayHostTests" + } + ] +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index ce8ff7dc38ba..1aeafa391b41 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -199,6 +199,11 @@ public abstract class ColorSpace { private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }; private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; + /** + * A gray color space does not have meaningful primaries, so we use this arbitrary set. + */ + private static final float[] GRAY_PRIMARIES = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f }; private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = @@ -1457,6 +1462,7 @@ public abstract class ColorSpace { "sRGB IEC61966-2.1", SRGB_PRIMARIES, ILLUMINANT_D65, + null, SRGB_TRANSFER_PARAMETERS, Named.SRGB.ordinal() ); @@ -1491,6 +1497,7 @@ public abstract class ColorSpace { "Rec. ITU-R BT.709-5", new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, ILLUMINANT_D65, + null, new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.BT709.ordinal() ); @@ -1498,6 +1505,7 @@ public abstract class ColorSpace { "Rec. ITU-R BT.2020-1", new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, ILLUMINANT_D65, + null, new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), Named.BT2020.ordinal() ); @@ -1513,6 +1521,7 @@ public abstract class ColorSpace { "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, ILLUMINANT_D65, + null, SRGB_TRANSFER_PARAMETERS, Named.DISPLAY_P3.ordinal() ); @@ -1520,6 +1529,7 @@ public abstract class ColorSpace { "NTSC (1953)", NTSC_1953_PRIMARIES, ILLUMINANT_C, + null, new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.NTSC_1953.ordinal() ); @@ -1527,6 +1537,7 @@ public abstract class ColorSpace { "SMPTE-C RGB", new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, ILLUMINANT_D65, + null, new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.SMPTE_C.ordinal() ); @@ -1542,6 +1553,7 @@ public abstract class ColorSpace { "ROMM RGB ISO 22028-2:2013", new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, ILLUMINANT_D50, + null, new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8), Named.PRO_PHOTO_RGB.ordinal() ); @@ -2471,7 +2483,11 @@ public abstract class ColorSpace { @NonNull @Size(min = 1) String name, @NonNull @Size(9) float[] toXYZ, @NonNull TransferParameters function) { - this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MIN_ID); + // Note: when isGray() returns false, this passes null for the transform for + // consistency with other constructors, which compute the transform from the primaries + // and white point. + this(name, isGray(toXYZ) ? GRAY_PRIMARIES : computePrimaries(toXYZ), + computeWhitePoint(toXYZ), isGray(toXYZ) ? toXYZ : null, function, MIN_ID); } /** @@ -2511,7 +2527,7 @@ public abstract class ColorSpace { @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull TransferParameters function) { - this(name, primaries, whitePoint, function, MIN_ID); + this(name, primaries, whitePoint, null, function, MIN_ID); } /** @@ -2534,6 +2550,8 @@ public abstract class ColorSpace { * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats + * @param transform Computed transform matrix that converts from RGB to XYZ, or + * {@code null} to compute it from {@code primaries} and {@code whitePoint}. * @param function Parameters for the transfer functions * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} * @@ -2552,9 +2570,10 @@ public abstract class ColorSpace { @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, + @Nullable @Size(9) float[] transform, @NonNull TransferParameters function, @IntRange(from = MIN_ID, to = MAX_ID) int id) { - this(name, primaries, whitePoint, null, + this(name, primaries, whitePoint, transform, function.e == 0.0 && function.f == 0.0 ? x -> rcpResponse(x, function.a, function.b, function.c, function.d, function.g) : @@ -2846,7 +2865,7 @@ public abstract class ColorSpace { * * @return The destination array passed as a parameter * - * @see #getWhitePoint(float[]) + * @see #getWhitePoint() */ @NonNull @Size(min = 2) @@ -2864,7 +2883,7 @@ public abstract class ColorSpace { * * @return A new non-null array of 2 floats * - * @see #getWhitePoint() + * @see #getWhitePoint(float[]) */ @NonNull @Size(2) @@ -2878,12 +2897,16 @@ public abstract class ColorSpace { * destination. The x and y components of the first primary are written * in the array at positions 0 and 1 respectively. * + * <p>Note: Some ColorSpaces represent gray profiles. The concept of + * primaries for such a ColorSpace does not make sense, so we use a special + * set of primaries that are all 1s.</p> + * * @param primaries The destination array, cannot be null, its length * must be >= 6 * * @return The destination array passed as a parameter * - * @see #getPrimaries(float[]) + * @see #getPrimaries() */ @NonNull @Size(min = 6) @@ -2898,9 +2921,13 @@ public abstract class ColorSpace { * the destination. The x and y components of the first primary are * written in the array at positions 0 and 1 respectively. * + * <p>Note: Some ColorSpaces represent gray profiles. The concept of + * primaries for such a ColorSpace does not make sense, so we use a special + * set of primaries that are all 1s.</p> + * * @return A new non-null array of 2 floats * - * @see #getWhitePoint() + * @see #getPrimaries(float[]) */ @NonNull @Size(6) @@ -2922,7 +2949,7 @@ public abstract class ColorSpace { * * @return The destination array passed as a parameter * - * @see #getInverseTransform() + * @see #getTransform() */ @NonNull @Size(min = 9) @@ -2942,7 +2969,7 @@ public abstract class ColorSpace { * * @return A new array of 9 floats * - * @see #getInverseTransform(float[]) + * @see #getTransform(float[]) */ @NonNull @Size(9) @@ -2964,7 +2991,7 @@ public abstract class ColorSpace { * * @return The destination array passed as a parameter * - * @see #getTransform() + * @see #getInverseTransform() */ @NonNull @Size(min = 9) @@ -2984,7 +3011,7 @@ public abstract class ColorSpace { * * @return A new array of 9 floats * - * @see #getTransform(float[]) + * @see #getInverseTransform(float[]) */ @NonNull @Size(9) @@ -3287,6 +3314,16 @@ public abstract class ColorSpace { return true; } + /** + * Report whether this matrix is a special gray matrix. + * @param toXYZ A XYZD50 matrix. Skia uses a special form for a gray profile. + * @return true if this is a special gray matrix. + */ + private static boolean isGray(@NonNull @Size(9) float[] toXYZ) { + return toXYZ.length == 9 && toXYZ[1] == 0 && toXYZ[2] == 0 && toXYZ[3] == 0 + && toXYZ[5] == 0 && toXYZ[6] == 0 && toXYZ[7] == 0; + } + private static boolean compare(double point, @NonNull DoubleUnaryOperator a, @NonNull DoubleUnaryOperator b) { double rA = a.applyAsDouble(point); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index d1b41dfccf63..9b4aebcd8aff 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -34,6 +34,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.PendingIntent; import android.app.PropertyInvalidatedCache; import android.compat.Compatibility; @@ -2561,7 +2562,7 @@ public class LocationManager { } public String getListenerId() { - return mConsumer.getClass().getName() + "@" + System.identityHashCode(mConsumer); + return AppOpsManager.toReceiverId(mConsumer); } public synchronized void register(AlarmManager alarmManager, @@ -2690,7 +2691,7 @@ public class LocationManager { } public String getListenerId() { - return mListener.getClass().getName() + "@" + System.identityHashCode(mListener); + return AppOpsManager.toReceiverId(mListener); } public void register(@NonNull Executor executor) { diff --git a/media/OWNERS b/media/OWNERS index be605831a24b..a16373ef8c8d 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -1,7 +1,7 @@ andrewlewis@google.com chz@google.com -dwkang@google.com elaurent@google.com +essick@google.com etalvala@google.com gkasten@google.com hdmoon@google.com @@ -15,7 +15,7 @@ klhyun@google.com lajos@google.com marcone@google.com sungsoo@google.com -wjia@google.com +wonsik@google.com # For maintaining sync with AndroidX code per-file ExifInterface.java = jinpark@google.com, sungsoo@google.com diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 25f6059c35de..bd8fb9602656 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -409,7 +409,7 @@ public final class MediaRouter2 { // TODO: Check thread-safety if (!mRoutes.containsKey(route.getId())) { - notifyTransferFailed(route); + notifyTransferFailure(route); return; } if (controller.getRoutingSessionInfo().getTransferableRoutes().contains(route.getId())) { @@ -588,14 +588,14 @@ public final class MediaRouter2 { if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // One way can be introducing 'reason'. - notifyTransferFailed(requestedRoute); + notifyTransferFailure(requestedRoute); return; } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) { Log.w(TAG, "The session does not contain the requested route. " + "(requestedRouteId=" + requestedRoute.getId() + ", actualRoutes=" + sessionInfo.getSelectedRoutes() + ")"); - notifyTransferFailed(requestedRoute); + notifyTransferFailure(requestedRoute); return; } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) { @@ -603,7 +603,7 @@ public final class MediaRouter2 { + "(requested route's providerId=" + requestedRoute.getProviderId() + ", actual providerId=" + sessionInfo.getProviderId() + ")"); - notifyTransferFailed(requestedRoute); + notifyTransferFailure(requestedRoute); return; } } @@ -619,7 +619,7 @@ public final class MediaRouter2 { } } //TODO: Determine oldController properly when transfer is launched by Output Switcher. - notifyTransferred(matchingRequest != null ? matchingRequest.mController : + notifyTransfer(matchingRequest != null ? matchingRequest.mController : getSystemController(), newController); } } @@ -687,15 +687,7 @@ public final class MediaRouter2 { return; } - boolean removed; - synchronized (sRouterLock) { - removed = mRoutingControllers.remove(uniqueSessionId, matchingController); - } - - if (removed) { - matchingController.release(); - notifyStopped(matchingController); - } + matchingController.releaseInternal(/* shouldReleaseSession= */ false); } private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, @@ -736,22 +728,21 @@ public final class MediaRouter2 { } } - private void notifyTransferred(RoutingController oldController, - RoutingController newController) { + private void notifyTransfer(RoutingController oldController, RoutingController newController) { for (TransferCallbackRecord record: mTransferCallbackRecords) { record.mExecutor.execute( () -> record.mTransferCallback.onTransfer(oldController, newController)); } } - private void notifyTransferFailed(MediaRoute2Info route) { + private void notifyTransferFailure(MediaRoute2Info route) { for (TransferCallbackRecord record: mTransferCallbackRecords) { record.mExecutor.execute( () -> record.mTransferCallback.onTransferFailure(route)); } } - private void notifyStopped(RoutingController controller) { + private void notifyStop(RoutingController controller) { for (TransferCallbackRecord record: mTransferCallbackRecords) { record.mExecutor.execute( () -> record.mTransferCallback.onStop(controller)); @@ -1189,32 +1180,38 @@ public final class MediaRouter2 { */ // TODO: Add tests using {@link MediaRouter2Manager#getActiveSessions()}. public void release() { + releaseInternal(/* shouldReleaseSession= */ true); + } + + void releaseInternal(boolean shouldReleaseSession) { synchronized (mControllerLock) { if (mIsReleased) { - Log.w(TAG, "release() called on released controller. Ignoring."); + Log.w(TAG, "releaseInternal() called on released controller. Ignoring."); return; } mIsReleased = true; } MediaRouter2Stub stub; - boolean removed; synchronized (sRouterLock) { - removed = mRoutingControllers.remove(getId(), this); + mRoutingControllers.remove(getId(), this); stub = mStub; } - if (removed) { - mHandler.post(() -> notifyStopped(RoutingController.this)); - } - - if (stub != null) { + if (shouldReleaseSession && stub != null) { try { mMediaRouterService.releaseSessionWithRouter2(stub, getId()); } catch (RemoteException ex) { - Log.e(TAG, "Unable to notify of controller release", ex); + Log.e(TAG, "Unable to release session", ex); } } + + if (Thread.currentThread() == mHandler.getLooper().getThread()) { + notifyStop(this); + } else { + mHandler.sendMessage(obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, + RoutingController.this)); + } } @Override diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 861eeea3bc34..fdd64f96701c 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -263,14 +263,14 @@ public class Tuner implements AutoCloseable { } private void setFrontendInfoList() { - List<Integer> ids = nativeGetFrontendIds(); + List<Integer> ids = getFrontendIds(); if (ids == null) { return; } TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()]; for (int i = 0; i < ids.size(); i++) { int id = ids.get(i); - FrontendInfo frontendInfo = nativeGetFrontendInfo(id); + FrontendInfo frontendInfo = getFrontendInfoById(id); if (frontendInfo == null) { continue; } @@ -281,6 +281,11 @@ public class Tuner implements AutoCloseable { mTunerResourceManager.setFrontendInfoList(infos); } + /** @hide */ + public List<Integer> getFrontendIds() { + return nativeGetFrontendIds(); + } + private void setLnbIds() { int[] ids = nativeGetLnbIds(); if (ids == null) { @@ -345,14 +350,17 @@ public class Tuner implements AutoCloseable { @Override public void close() { if (mFrontendHandle != null) { + nativeCloseFrontendByHandle(mFrontendHandle); mTunerResourceManager.releaseFrontend(mFrontendHandle); mFrontendHandle = null; + mFrontend = null; } if (mLnb != null) { mTunerResourceManager.releaseLnb(mLnbHandle); mLnb = null; + mLnbHandle = null; } - nativeClose(); + TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); } /** @@ -374,6 +382,8 @@ public class Tuner implements AutoCloseable { * Native method to open frontend of the given ID. */ private native Frontend nativeOpenFrontendByHandle(int handle); + @Result + private native int nativeCloseFrontendByHandle(int handle); private native int nativeTune(int type, FrontendSettings settings); private native int nativeStopTune(); private native int nativeScan(int settingsType, FrontendSettings settings, int scanType); @@ -522,6 +532,7 @@ public class Tuner implements AutoCloseable { public int tune(@NonNull FrontendSettings settings) { mFrontendType = settings.getType(); checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND); + mFrontendInfo = null; return nativeTune(settings.getType(), settings); } @@ -706,11 +717,16 @@ public class Tuner implements AutoCloseable { throw new IllegalStateException("frontend is not initialized"); } if (mFrontendInfo == null) { - mFrontendInfo = nativeGetFrontendInfo(mFrontend.mId); + mFrontendInfo = getFrontendInfoById(mFrontend.mId); } return mFrontendInfo; } + /** @hide */ + public FrontendInfo getFrontendInfoById(int id) { + return nativeGetFrontendInfo(id); + } + /** * Gets Demux capabilities. * diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index 2c8899cfca78..a9f3dcf150cb 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -416,11 +416,11 @@ public class TunerResourceManager { * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called * before this release. * - * @param frontendId the id of the released frontend. + * @param frontendHandle the handle of the released frontend. */ - public void releaseFrontend(int frontendId) { + public void releaseFrontend(int frontendHandle) { try { - mService.releaseFrontend(frontendId); + mService.releaseFrontend(frontendHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index a31f177d66ab..362dfa0c88f4 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -132,6 +132,8 @@ static struct { jmethodID asReadOnlyBufferId; jmethodID positionId; jmethodID limitId; + jmethodID getPositionId; + jmethodID getLimitId; } gByteBufferInfo; static struct { @@ -2033,13 +2035,11 @@ static status_t ConvertKeyValueListsToAMessage( if (env->IsInstanceOf(jvalue.get(), sFields.mStringClass)) { const char *tmp = env->GetStringUTFChars((jstring)jvalue.get(), nullptr); AString value; - if (tmp) { - value.setTo(tmp); - } - env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp); - if (value.empty()) { + if (!tmp) { return NO_MEMORY; } + value.setTo(tmp); + env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp); result->setString(key.c_str(), value); } else if (env->IsInstanceOf(jvalue.get(), sFields.mIntegerClass)) { jint value = env->CallIntMethod(jvalue.get(), sFields.mIntegerValueId); @@ -2051,8 +2051,8 @@ static status_t ConvertKeyValueListsToAMessage( jfloat value = env->CallFloatMethod(jvalue.get(), sFields.mFloatValueId); result->setFloat(key.c_str(), value); } else if (env->IsInstanceOf(jvalue.get(), gByteBufferInfo.clazz)) { - jint position = env->CallIntMethod(jvalue.get(), gByteBufferInfo.positionId); - jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.limitId); + jint position = env->CallIntMethod(jvalue.get(), gByteBufferInfo.getPositionId); + jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.getLimitId); sp<ABuffer> buffer{new ABuffer(limit - position)}; void *data = env->GetDirectBufferAddress(jvalue.get()); if (data != nullptr) { @@ -2773,6 +2773,14 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { clazz.get(), "limit", "(I)Ljava/nio/Buffer;"); CHECK(gByteBufferInfo.limitId != NULL); + gByteBufferInfo.getPositionId = env->GetMethodID( + clazz.get(), "position", "()I"); + CHECK(gByteBufferInfo.getPositionId != NULL); + + gByteBufferInfo.getLimitId = env->GetMethodID( + clazz.get(), "limit", "()I"); + CHECK(gByteBufferInfo.getLimitId != NULL); + clazz.reset(env->FindClass("java/util/ArrayList")); CHECK(clazz.get() != NULL); diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 7579ca58b37b..909394fbc495 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -833,6 +833,12 @@ JTuner::JTuner(JNIEnv *env, jobject thiz) } JTuner::~JTuner() { + if (mFe != NULL) { + mFe->close(); + } + if (mDemux != NULL) { + mDemux->close(); + } JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mObject); @@ -908,6 +914,14 @@ jobject JTuner::openFrontendById(int id) { (jint) jId); } +jint JTuner::closeFrontendById(int id) { + if (mFe != NULL && mFeId == id) { + Result r = mFe->close(); + return (jint) r; + } + return (jint) Result::SUCCESS; +} + jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); @@ -1271,6 +1285,23 @@ Result JTuner::openDemux() { return res; } +jint JTuner::close() { + Result res = Result::SUCCESS; + if (mFe != NULL) { + res = mFe->close(); + if (res != Result::SUCCESS) { + return (jint) res; + } + } + if (mDemux != NULL) { + res = mDemux->close(); + if (res != Result::SUCCESS) { + return (jint) res; + } + } + return (jint) res; +} + jobject JTuner::getAvSyncHwId(sp<Filter> filter) { if (mDemux == NULL) { return NULL; @@ -2362,6 +2393,13 @@ static jobject android_media_tv_Tuner_open_frontend_by_handle( return tuner->openFrontendById(id); } +static jint android_media_tv_Tuner_close_frontend_by_handle( + JNIEnv *env, jobject thiz, jint handle) { + sp<JTuner> tuner = getTuner(env, thiz); + uint32_t id = getResourceIdFromHandle(handle); + return tuner->closeFrontendById(id); +} + static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->tune(getFrontendSettings(env, type, settings)); @@ -3135,6 +3173,11 @@ static jint android_media_tv_Tuner_open_demux(JNIEnv* env, jobject thiz, jint /* return (jint) tuner->openDemux(); } +static jint android_media_tv_Tuner_close_tuner(JNIEnv* env, jobject thiz) { + sp<JTuner> tuner = getTuner(env, thiz); + return (jint) tuner->close(); +} + static jint android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) { sp<Dvr> dvrSp = getDvr(env, dvr); if (dvrSp == NULL) { @@ -3424,6 +3467,8 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_get_frontend_ids }, { "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;", (void *)android_media_tv_Tuner_open_frontend_by_handle }, + { "nativeCloseFrontendByHandle", "(I)I", + (void *)android_media_tv_Tuner_close_frontend_by_handle }, { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I", (void *)android_media_tv_Tuner_tune }, { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune }, @@ -3460,6 +3505,7 @@ static const JNINativeMethod gTunerMethods[] = { { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;", (void *)android_media_tv_Tuner_get_demux_caps }, { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux }, + {"nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner }, }; static const JNINativeMethod gFilterMethods[] = { diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 6749ba085739..750b146dbd59 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -172,6 +172,7 @@ struct JTuner : public RefBase { int disconnectCiCam(); jobject getFrontendIds(); jobject openFrontendById(int id); + jint closeFrontendById(int id); jobject getFrontendInfo(int id); int tune(const FrontendSettings& settings); int stopTune(); @@ -189,6 +190,7 @@ struct JTuner : public RefBase { jobject getDemuxCaps(); jobject getFrontendStatus(jintArray types); Result openDemux(); + jint close(); protected: virtual ~JTuner(); diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp new file mode 100644 index 000000000000..ed3383752695 --- /dev/null +++ b/media/tests/AudioPolicyTest/Android.bp @@ -0,0 +1,17 @@ +android_test { + name: "audiopolicytest", + srcs: ["**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "mockito-target-minus-junit4", + "androidx.test.rules", + "android-ex-camera2", + "testng", + ], + platform_apis: true, + certificate: "platform", + resource_dirs: ["res"], +} diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml new file mode 100644 index 000000000000..adb058c82870 --- /dev/null +++ b/media/tests/AudioPolicyTest/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.audiopolicytest"> + + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> + <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" /> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:label="@string/app_name" android:name="AudioPolicyTest" + android:screenOrientation="landscape"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + + <!--instrumentation android:name=".AudioPolicyTestRunner" + android:targetPackage="com.android.audiopolicytest" + android:label="AudioManager policy oriented integration tests InstrumentationRunner"> + </instrumentation--> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.audiopolicytest" + android:label="AudioManager policy oriented integration tests InstrumentationRunner"> + </instrumentation> +</manifest> diff --git a/media/tests/AudioPolicyTest/AndroidTest.xml b/media/tests/AudioPolicyTest/AndroidTest.xml new file mode 100644 index 000000000000..f3ca9a165d1b --- /dev/null +++ b/media/tests/AudioPolicyTest/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Media Framework Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="audiopolicytest.apk" /> + </target_preparer> + + <option name="test-tag" value="AudioPolicyTest" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.audiopolicytest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/media/tests/AudioPolicyTest/res/layout/audiopolicytest.xml b/media/tests/AudioPolicyTest/res/layout/audiopolicytest.xml new file mode 100644 index 000000000000..17fdba6f7c15 --- /dev/null +++ b/media/tests/AudioPolicyTest/res/layout/audiopolicytest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> +</LinearLayout> diff --git a/media/tests/AudioPolicyTest/res/values/strings.xml b/media/tests/AudioPolicyTest/res/values/strings.xml new file mode 100644 index 000000000000..036592770450 --- /dev/null +++ b/media/tests/AudioPolicyTest/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- name of the app [CHAR LIMIT=25]--> + <string name="app_name">Audio Policy APIs Tests</string> +</resources> diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java new file mode 100644 index 000000000000..1131c623e428 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.audiopolicytest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.testng.Assert.assertThrows; + +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.audiopolicy.AudioProductStrategy; +import android.media.audiopolicy.AudioVolumeGroup; +import android.util.Log; + +import com.google.common.primitives.Ints; + +import java.util.List; + +public class AudioManagerTest extends AudioVolumesTestBase { + private static final String TAG = "AudioManagerTest"; + + //----------------------------------------------------------------- + // Test getAudioProductStrategies and validate strategies + //----------------------------------------------------------------- + public void testGetAndValidateProductStrategies() throws Exception { + List<AudioProductStrategy> audioProductStrategies = + mAudioManager.getAudioProductStrategies(); + assertTrue(audioProductStrategies.size() > 0); + + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + assertTrue(audioVolumeGroups.size() > 0); + + // Validate Audio Product Strategies + for (final AudioProductStrategy audioProductStrategy : audioProductStrategies) { + AudioAttributes attributes = audioProductStrategy.getAudioAttributes(); + int strategyStreamType = + audioProductStrategy.getLegacyStreamTypeForAudioAttributes(attributes); + + assertTrue("Strategy shall support the attributes retrieved from its getter API", + audioProductStrategy.supportsAudioAttributes(attributes)); + + int volumeGroupId = + audioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes); + + // A strategy must be associated to a volume group + assertNotEquals("strategy not assigned to any volume group", + volumeGroupId, AudioVolumeGroup.DEFAULT_VOLUME_GROUP); + + // Valid Group ? + AudioVolumeGroup audioVolumeGroup = null; + for (final AudioVolumeGroup avg : audioVolumeGroups) { + if (avg.getId() == volumeGroupId) { + audioVolumeGroup = avg; + break; + } + } + assertNotNull("Volume Group not found", audioVolumeGroup); + + // Cross check: the group shall have at least one aa / stream types following the + // considered strategy + boolean strategyAttributesSupported = false; + for (final AudioAttributes aa : audioVolumeGroup.getAudioAttributes()) { + if (audioProductStrategy.supportsAudioAttributes(aa)) { + strategyAttributesSupported = true; + break; + } + } + assertTrue("Volume Group and Strategy mismatching", strategyAttributesSupported); + + // Some Product strategy may not have corresponding stream types as they intends + // to address volume setting per attributes to avoid adding new stream type + // and going on deprecating the stream type even for volume + if (strategyStreamType != AudioSystem.STREAM_DEFAULT) { + boolean strategStreamTypeSupported = false; + for (final int vgStreamType : audioVolumeGroup.getLegacyStreamTypes()) { + if (vgStreamType == strategyStreamType) { + strategStreamTypeSupported = true; + break; + } + } + assertTrue("Volume Group and Strategy mismatching", strategStreamTypeSupported); + } + } + } + + //----------------------------------------------------------------- + // Test getAudioVolumeGroups and validate volume groups + //----------------------------------------------------------------- + + public void testGetAndValidateVolumeGroups() throws Exception { + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + assertTrue(audioVolumeGroups.size() > 0); + + List<AudioProductStrategy> audioProductStrategies = + mAudioManager.getAudioProductStrategies(); + assertTrue(audioProductStrategies.size() > 0); + + // Validate Audio Volume Groups, check all + for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) { + List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes(); + int[] avgStreamTypes = audioVolumeGroup.getLegacyStreamTypes(); + + // for each volume group attributes, find the matching product strategy and ensure + // it is linked the considered volume group + for (final AudioAttributes aa : avgAttributes) { + if (aa.equals(sDefaultAttributes)) { + // Some volume groups may not have valid attributes, used for internal + // volume management like patch/rerouting + // so bailing out strategy retrieval from attributes + continue; + } + boolean isVolumeGroupAssociatedToStrategy = false; + for (final AudioProductStrategy strategy : audioProductStrategies) { + int groupId = strategy.getVolumeGroupIdForAudioAttributes(aa); + if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + + assertEquals("Volume Group ID (" + audioVolumeGroup.toString() + + "), and Volume group ID associated to Strategy (" + + strategy.toString() + ") both supporting attributes " + + aa.toString() + " are mismatching", + audioVolumeGroup.getId(), groupId); + isVolumeGroupAssociatedToStrategy = true; + break; + } + } + assertTrue("Volume Group (" + audioVolumeGroup.toString() + + ") has no associated strategy for attributes " + aa.toString(), + isVolumeGroupAssociatedToStrategy); + } + + // for each volume group stream type, find the matching product strategy and ensure + // it is linked the considered volume group + for (final int avgStreamType : avgStreamTypes) { + if (avgStreamType == AudioSystem.STREAM_DEFAULT) { + // Some Volume Groups may not have corresponding stream types as they + // intends to address volume setting per attributes to avoid adding new + // stream type and going on deprecating the stream type even for volume + // so bailing out strategy retrieval from stream type + continue; + } + boolean isVolumeGroupAssociatedToStrategy = false; + for (final AudioProductStrategy strategy : audioProductStrategies) { + Log.i(TAG, "strategy:" + strategy.toString()); + int groupId = strategy.getVolumeGroupIdForLegacyStreamType(avgStreamType); + if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + + assertEquals("Volume Group ID (" + audioVolumeGroup.toString() + + "), and Volume group ID associated to Strategy (" + + strategy.toString() + ") both supporting stream " + + AudioSystem.streamToString(avgStreamType) + "(" + + avgStreamType + ") are mismatching", + audioVolumeGroup.getId(), groupId); + isVolumeGroupAssociatedToStrategy = true; + break; + } + } + assertTrue("Volume Group (" + audioVolumeGroup.toString() + + ") has no associated strategy for stream " + + AudioSystem.streamToString(avgStreamType) + "(" + avgStreamType + ")", + isVolumeGroupAssociatedToStrategy); + } + } + } + + //----------------------------------------------------------------- + // Test Volume per Attributes setter/getters + //----------------------------------------------------------------- + public void testSetGetVolumePerAttributesWithInvalidAttributes() throws Exception { + AudioAttributes nullAttributes = null; + + assertThrows(NullPointerException.class, + () -> mAudioManager.getMaxVolumeIndexForAttributes(nullAttributes)); + + assertThrows(NullPointerException.class, + () -> mAudioManager.getMinVolumeIndexForAttributes(nullAttributes)); + + assertThrows(NullPointerException.class, + () -> mAudioManager.getVolumeIndexForAttributes(nullAttributes)); + + assertThrows(NullPointerException.class, + () -> mAudioManager.setVolumeIndexForAttributes( + nullAttributes, 0 /*index*/, 0/*flags*/)); + } + + public void testSetGetVolumePerAttributes() throws Exception { + for (int usage : AudioAttributes.SDK_USAGES) { + if (usage == AudioAttributes.USAGE_UNKNOWN) { + continue; + } + AudioAttributes aaForUsage = new AudioAttributes.Builder().setUsage(usage).build(); + int indexMin = 0; + int indexMax = 0; + int index = 0; + Exception ex = null; + + try { + indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aaForUsage); + } catch (Exception e) { + ex = e; // unexpected + } + assertNull("Exception was thrown for valid attributes", ex); + ex = null; + try { + indexMin = mAudioManager.getMinVolumeIndexForAttributes(aaForUsage); + } catch (Exception e) { + ex = e; // unexpected + } + assertNull("Exception was thrown for valid attributes", ex); + ex = null; + try { + index = mAudioManager.getVolumeIndexForAttributes(aaForUsage); + } catch (Exception e) { + ex = e; // unexpected + } + assertNull("Exception was thrown for valid attributes", ex); + ex = null; + try { + mAudioManager.setVolumeIndexForAttributes(aaForUsage, indexMin, 0/*flags*/); + } catch (Exception e) { + ex = e; // unexpected + } + assertNull("Exception was thrown for valid attributes", ex); + + index = mAudioManager.getVolumeIndexForAttributes(aaForUsage); + assertEquals(index, indexMin); + + mAudioManager.setVolumeIndexForAttributes(aaForUsage, indexMax, 0/*flags*/); + index = mAudioManager.getVolumeIndexForAttributes(aaForUsage); + assertEquals(index, indexMax); + } + } + + //----------------------------------------------------------------- + // Test register/unregister VolumeGroupCallback + //----------------------------------------------------------------- + public void testVolumeGroupCallback() throws Exception { + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + assertTrue(audioVolumeGroups.size() > 0); + + AudioVolumeGroupCallbackHelper vgCbReceiver = new AudioVolumeGroupCallbackHelper(); + mAudioManager.registerVolumeGroupCallback(mContext.getMainExecutor(), vgCbReceiver); + + final List<Integer> publicStreams = Ints.asList(PUBLIC_STREAM_TYPES); + try { + // Validate Audio Volume Groups callback reception + for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) { + int volumeGroupId = audioVolumeGroup.getId(); + + // Set the receiver to filter only the current group callback + vgCbReceiver.setExpectedVolumeGroup(volumeGroupId); + + List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes(); + int[] avgStreamTypes = audioVolumeGroup.getLegacyStreamTypes(); + + int index = 0; + int indexMax = 0; + int indexMin = 0; + + // Set the volume per attributes (if valid) and wait the callback + for (final AudioAttributes aa : avgAttributes) { + if (aa.equals(sDefaultAttributes)) { + // Some volume groups may not have valid attributes, used for internal + // volume management like patch/rerouting + // so bailing out strategy retrieval from attributes + continue; + } + index = mAudioManager.getVolumeIndexForAttributes(aa); + indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa); + indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa); + index = incrementVolumeIndex(index, indexMin, indexMax); + + vgCbReceiver.setExpectedVolumeGroup(volumeGroupId); + mAudioManager.setVolumeIndexForAttributes(aa, index, 0/*flags*/); + assertTrue(vgCbReceiver.waitForExpectedVolumeGroupChanged( + AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS)); + + int readIndex = mAudioManager.getVolumeIndexForAttributes(aa); + assertEquals(readIndex, index); + } + // Set the volume per stream type (if valid) and wait the callback + for (final int avgStreamType : avgStreamTypes) { + if (avgStreamType == AudioSystem.STREAM_DEFAULT) { + // Some Volume Groups may not have corresponding stream types as they + // intends to address volume setting per attributes to avoid adding new + // stream type and going on deprecating the stream type even for volume + // so bailing out strategy retrieval from stream type + continue; + } + if (!publicStreams.contains(avgStreamType) + || avgStreamType == AudioManager.STREAM_ACCESSIBILITY) { + // Limit scope of test to public stream that do not require any + // permission (e.g. Changing ACCESSIBILITY is subject to permission). + continue; + } + index = mAudioManager.getStreamVolume(avgStreamType); + indexMax = mAudioManager.getStreamMaxVolume(avgStreamType); + indexMin = mAudioManager.getStreamMinVolumeInt(avgStreamType); + index = incrementVolumeIndex(index, indexMin, indexMax); + + vgCbReceiver.setExpectedVolumeGroup(volumeGroupId); + mAudioManager.setStreamVolume(avgStreamType, index, 0/*flags*/); + assertTrue(vgCbReceiver.waitForExpectedVolumeGroupChanged( + AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS)); + + int readIndex = mAudioManager.getStreamVolume(avgStreamType); + assertEquals(index, readIndex); + } + } + } finally { + mAudioManager.unregisterVolumeGroupCallback(vgCbReceiver); + } + } +} diff --git a/core/java/android/view/inline/InlinePresentationSpec.aidl b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java index 680ee4e14b54..e0c7b223a2e4 100644 --- a/core/java/android/view/inline/InlinePresentationSpec.aidl +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,25 @@ * limitations under the License. */ -package android.view.inline; +package com.android.audiopolicytest; -/** - * @hide - * @removed - */ -parcelable InlinePresentationSpec; +import android.app.Activity; +import android.os.Bundle; + +public class AudioPolicyTest extends Activity { + + public AudioPolicyTest() { + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.audiopolicytest); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java new file mode 100644 index 000000000000..c0f596b974e1 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.audiopolicytest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.media.AudioAttributes; +import android.media.AudioSystem; +import android.media.audiopolicy.AudioProductStrategy; +import android.media.audiopolicy.AudioVolumeGroup; +import android.util.Log; + +import java.util.List; + +public class AudioProductStrategyTest extends AudioVolumesTestBase { + private static final String TAG = "AudioProductStrategyTest"; + + //----------------------------------------------------------------- + // Test getAudioProductStrategies and validate strategies + //----------------------------------------------------------------- + public void testGetProductStrategies() throws Exception { + List<AudioProductStrategy> audioProductStrategies = + AudioProductStrategy.getAudioProductStrategies(); + + assertNotNull(audioProductStrategies); + assertTrue(audioProductStrategies.size() > 0); + + for (final AudioProductStrategy aps : audioProductStrategies) { + assertTrue(aps.getId() >= 0); + + AudioAttributes aa = aps.getAudioAttributes(); + assertNotNull(aa); + + // Ensure API consistency + assertTrue(aps.supportsAudioAttributes(aa)); + + int streamType = aps.getLegacyStreamTypeForAudioAttributes(aa); + if (streamType == AudioSystem.STREAM_DEFAULT) { + // bailing out test for volume group APIs consistency + continue; + } + final int volumeGroupFromStream = aps.getVolumeGroupIdForLegacyStreamType(streamType); + final int volumeGroupFromAttributes = aps.getVolumeGroupIdForAudioAttributes(aa); + assertNotEquals(volumeGroupFromStream, AudioVolumeGroup.DEFAULT_VOLUME_GROUP); + assertEquals(volumeGroupFromStream, volumeGroupFromAttributes); + } + } + + //----------------------------------------------------------------- + // Test stream to/from attributes conversion + //----------------------------------------------------------------- + public void testAudioAttributesFromStreamTypes() throws Exception { + List<AudioProductStrategy> audioProductStrategies = + AudioProductStrategy.getAudioProductStrategies(); + + assertNotNull(audioProductStrategies); + assertTrue(audioProductStrategies.size() > 0); + + for (final int streamType : PUBLIC_STREAM_TYPES) { + AudioAttributes aaFromStreamType = + AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType( + streamType); + + // No strategy found for this stream type or no attributes defined for the strategy + // hosting this stream type; Bailing out the test, just ensure that any request + // for reciproque API with the unknown attributes would return default stream + // for volume control, aka STREAM_MUSIC. + if (aaFromStreamType.equals(sInvalidAttributes)) { + assertEquals(AudioSystem.STREAM_MUSIC, + AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes( + aaFromStreamType)); + } else { + // Attributes are valid, i.e. a strategy was found supporting this stream type + // with valid attributes. Ensure reciproque works fine + int streamTypeFromAttributes = + AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes( + aaFromStreamType); + assertEquals("stream " + AudioSystem.streamToString(streamType) + "(" + + streamType + ") expected to match attributes " + + aaFromStreamType.toString() + " got instead stream " + + AudioSystem.streamToString(streamTypeFromAttributes) + "(" + + streamTypeFromAttributes + ") expected to match attributes ", + streamType, streamTypeFromAttributes); + } + + // Now identify the strategy supporting this stream type, ensure uniqueness + boolean strategyFound = false; + for (final AudioProductStrategy aps : audioProductStrategies) { + AudioAttributes aaFromAps = + aps.getAudioAttributesForLegacyStreamType(streamType); + + if (aaFromAps == null) { + // not this one... + continue; + } + // Got it! + assertFalse("Unique ProductStrategy shall match for a given stream type", + strategyFound); + strategyFound = true; + + // Ensure getters aligned + assertEquals(aaFromStreamType, aaFromAps); + assertTrue(aps.supportsAudioAttributes(aaFromStreamType)); + + // Ensure reciproque works fine + assertEquals(streamType, + aps.getLegacyStreamTypeForAudioAttributes(aaFromStreamType)); + + // Ensure consistency of volume group getter API + final int volumeGroupFromStream = + aps.getVolumeGroupIdForLegacyStreamType(streamType); + final int volumeGroupFromAttributes = + aps.getVolumeGroupIdForAudioAttributes(aaFromStreamType); + assertNotEquals(volumeGroupFromStream, AudioVolumeGroup.DEFAULT_VOLUME_GROUP); + assertEquals(volumeGroupFromStream, volumeGroupFromAttributes); + } + if (!strategyFound) { + // No strategy found, ensure volume control is MUSIC + assertEquals(AudioSystem.STREAM_MUSIC, + AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes( + aaFromStreamType)); + } + } + } + + public void testAudioAttributesToStreamTypes() throws Exception { + List<AudioProductStrategy> audioProductStrategies = + AudioProductStrategy.getAudioProductStrategies(); + + assertNotNull(audioProductStrategies); + assertTrue(audioProductStrategies.size() > 0); + + for (int usage : AudioAttributes.SDK_USAGES) { + AudioAttributes aaForUsage = new AudioAttributes.Builder().setUsage(usage).build(); + + int streamTypeFromUsage = + AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes( + aaForUsage); + + // Cannot be undefined, always shall fall back on a valid stream type + // to be able to control the volume + assertNotEquals(streamTypeFromUsage, AudioSystem.STREAM_DEFAULT); + + Log.w(TAG, "GUSTAVE aaForUsage=" + aaForUsage.toString()); + + // Now identify the strategy hosting these Audio Attributes and ensure informations + // matches. + // Now identify the strategy supporting this stream type, ensure uniqueness + boolean strategyFound = false; + for (final AudioProductStrategy aps : audioProductStrategies) { + if (!aps.supportsAudioAttributes(aaForUsage)) { + // Not this one + continue; + } + // Got it! + String msg = "Unique ProductStrategy shall match for a given audio attributes " + + aaForUsage.toString() + " already associated also matches with" + + aps.toString(); + assertFalse(msg, strategyFound); + strategyFound = true; + + // It may not return the expected stream type if the strategy does not have + // associated stream type. + // Behavior of member function getLegacyStreamTypeForAudioAttributes is + // different than getLegacyStreamTypeForStrategyWithAudioAttributes since it + // does not fallback on MUSIC stream type for volume operation + int streamTypeFromAps = aps.getLegacyStreamTypeForAudioAttributes(aaForUsage); + if (streamTypeFromAps == AudioSystem.STREAM_DEFAULT) { + // No stream type assigned to this strategy + // Expect static API to return default stream type for volume (aka MUSIC) + assertEquals("Strategy (" + aps.toString() + ") has no associated stream " + + ", must fallback on MUSIC stream as default", + streamTypeFromUsage, AudioSystem.STREAM_MUSIC); + } else { + assertEquals("Attributes " + aaForUsage.toString() + " associated to stream " + + AudioSystem.streamToString(streamTypeFromUsage) + + " are supported by strategy (" + aps.toString() + ") which reports " + + " these attributes are associated to stream " + + AudioSystem.streamToString(streamTypeFromAps), + streamTypeFromUsage, streamTypeFromAps); + + // Ensure consistency of volume group getter API + int volumeGroupFromStream = + aps.getVolumeGroupIdForLegacyStreamType(streamTypeFromAps); + int volumeGroupFromAttributes = + aps.getVolumeGroupIdForAudioAttributes(aaForUsage); + assertNotEquals( + volumeGroupFromStream, AudioVolumeGroup.DEFAULT_VOLUME_GROUP); + assertEquals(volumeGroupFromStream, volumeGroupFromAttributes); + } + } + if (!strategyFound) { + // No strategy found for the given attributes, the expected stream must be MUSIC + assertEquals(streamTypeFromUsage, AudioSystem.STREAM_MUSIC); + } + } + } +} diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupCallbackHelper.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupCallbackHelper.java new file mode 100644 index 000000000000..0c1d52c57020 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupCallbackHelper.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.audiopolicytest; + +import static org.junit.Assert.assertNotNull; + +import android.media.AudioManager; +import android.util.Log; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + + +final class AudioVolumeGroupCallbackHelper extends AudioManager.VolumeGroupCallback { + private static final String TAG = "AudioVolumeGroupCallbackHelper"; + public static final long ASYNC_TIMEOUT_MS = 800; + + private int mExpectedVolumeGroupId; + + private CountDownLatch mVolumeGroupChanged = null; + + void setExpectedVolumeGroup(int group) { + mVolumeGroupChanged = new CountDownLatch(1); + mExpectedVolumeGroupId = group; + } + + @Override + public void onAudioVolumeGroupChanged(int group, int flags) { + if (group != mExpectedVolumeGroupId) { + return; + } + if (mVolumeGroupChanged == null) { + Log.wtf(TAG, "Received callback but object not initialized"); + return; + } + if (mVolumeGroupChanged.getCount() <= 0) { + Log.i(TAG, "callback for group: " + group + " already received"); + return; + } + mVolumeGroupChanged.countDown(); + } + + public boolean waitForExpectedVolumeGroupChanged(long timeOutMs) { + assertNotNull("Call first setExpectedVolumeGroup before waiting...", mVolumeGroupChanged); + boolean timeoutReached = false; + if (mVolumeGroupChanged.getCount() == 0) { + // done already... + return true; + } + try { + timeoutReached = !mVolumeGroupChanged.await(ASYNC_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { } + return !timeoutReached; + } +} diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java new file mode 100644 index 000000000000..221f1f7fef17 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.audiopolicytest; + +import static org.junit.Assert.assertEquals; +import static org.testng.Assert.assertThrows; + +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.audiopolicy.AudioVolumeGroup; +import android.media.audiopolicy.AudioVolumeGroupChangeHandler; + +import java.util.ArrayList; +import java.util.List; + +public class AudioVolumeGroupChangeHandlerTest extends AudioVolumesTestBase { + private static final String TAG = "AudioVolumeGroupChangeHandlerTest"; + + public void testRegisterInvalidCallback() throws Exception { + final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler = + new AudioVolumeGroupChangeHandler(); + + audioAudioVolumeGroupChangedHandler.init(); + + assertThrows(NullPointerException.class, () -> { + AudioManager.VolumeGroupCallback nullCb = null; + audioAudioVolumeGroupChangedHandler.registerListener(nullCb); + }); + } + + public void testUnregisterInvalidCallback() throws Exception { + final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler = + new AudioVolumeGroupChangeHandler(); + + audioAudioVolumeGroupChangedHandler.init(); + + final AudioVolumeGroupCallbackHelper cb = new AudioVolumeGroupCallbackHelper(); + audioAudioVolumeGroupChangedHandler.registerListener(cb); + + assertThrows(NullPointerException.class, () -> { + AudioManager.VolumeGroupCallback nullCb = null; + audioAudioVolumeGroupChangedHandler.unregisterListener(nullCb); + }); + audioAudioVolumeGroupChangedHandler.unregisterListener(cb); + } + + public void testRegisterUnregisterCallback() throws Exception { + final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler = + new AudioVolumeGroupChangeHandler(); + + audioAudioVolumeGroupChangedHandler.init(); + final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper(); + + // Should not assert, otherwise test will fail + audioAudioVolumeGroupChangedHandler.registerListener(validCb); + + // Should not assert, otherwise test will fail + audioAudioVolumeGroupChangedHandler.unregisterListener(validCb); + } + + public void testCallbackReceived() throws Exception { + final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler = + new AudioVolumeGroupChangeHandler(); + + audioAudioVolumeGroupChangedHandler.init(); + + final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper(); + audioAudioVolumeGroupChangedHandler.registerListener(validCb); + + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + assertTrue(audioVolumeGroups.size() > 0); + + try { + for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) { + int volumeGroupId = audioVolumeGroup.getId(); + + List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes(); + // Set the volume per attributes (if valid) and wait the callback + if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(sDefaultAttributes)) { + // Some volume groups may not have valid attributes, used for internal + // volume management like patch/rerouting + // so bailing out strategy retrieval from attributes + continue; + } + final AudioAttributes aa = avgAttributes.get(0); + + int index = mAudioManager.getVolumeIndexForAttributes(aa); + int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa); + int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa); + + final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax); + + // Set the receiver to filter only the current group callback + validCb.setExpectedVolumeGroup(volumeGroupId); + mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/); + assertTrue(validCb.waitForExpectedVolumeGroupChanged( + AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS)); + + final int readIndex = mAudioManager.getVolumeIndexForAttributes(aa); + assertEquals(readIndex, indexForAa); + } + } finally { + audioAudioVolumeGroupChangedHandler.unregisterListener(validCb); + } + } + + public void testMultipleCallbackReceived() throws Exception { + + final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler = + new AudioVolumeGroupChangeHandler(); + + audioAudioVolumeGroupChangedHandler.init(); + + final int callbackCount = 10; + final List<AudioVolumeGroupCallbackHelper> validCbs = + new ArrayList<AudioVolumeGroupCallbackHelper>(); + for (int i = 0; i < callbackCount; i++) { + validCbs.add(new AudioVolumeGroupCallbackHelper()); + } + for (final AudioVolumeGroupCallbackHelper cb : validCbs) { + audioAudioVolumeGroupChangedHandler.registerListener(cb); + } + + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + assertTrue(audioVolumeGroups.size() > 0); + + try { + for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) { + int volumeGroupId = audioVolumeGroup.getId(); + + List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes(); + // Set the volume per attributes (if valid) and wait the callback + if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(sDefaultAttributes)) { + // Some volume groups may not have valid attributes, used for internal + // volume management like patch/rerouting + // so bailing out strategy retrieval from attributes + continue; + } + AudioAttributes aa = avgAttributes.get(0); + + int index = mAudioManager.getVolumeIndexForAttributes(aa); + int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa); + int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa); + + final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax); + + // Set the receiver to filter only the current group callback + for (final AudioVolumeGroupCallbackHelper cb : validCbs) { + cb.setExpectedVolumeGroup(volumeGroupId); + } + mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/); + + for (final AudioVolumeGroupCallbackHelper cb : validCbs) { + assertTrue(cb.waitForExpectedVolumeGroupChanged( + AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS)); + } + int readIndex = mAudioManager.getVolumeIndexForAttributes(aa); + assertEquals(readIndex, indexForAa); + } + } finally { + for (final AudioVolumeGroupCallbackHelper cb : validCbs) { + audioAudioVolumeGroupChangedHandler.unregisterListener(cb); + } + } + } +} diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java new file mode 100644 index 000000000000..84b24b8fcab3 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.audiopolicytest; + +import static org.junit.Assert.assertNotEquals; + +import android.media.AudioAttributes; +import android.media.AudioSystem; +import android.media.audiopolicy.AudioProductStrategy; +import android.media.audiopolicy.AudioVolumeGroup; + +import java.util.List; + +public class AudioVolumeGroupTest extends AudioVolumesTestBase { + private static final String TAG = "AudioVolumeGroupTest"; + + //----------------------------------------------------------------- + // Test getAudioVolumeGroups and validate groud id + //----------------------------------------------------------------- + public void testGetVolumeGroupsFromNonServiceCaller() throws Exception { + // The transaction behind getAudioVolumeGroups will fail. Check is done at binder level + // with policy service. Error is not reported, the list is just empty. + // Request must come from service components + List<AudioVolumeGroup> audioVolumeGroup = AudioVolumeGroup.getAudioVolumeGroups(); + + assertNotNull(audioVolumeGroup); + assertEquals(audioVolumeGroup.size(), 0); + } + + //----------------------------------------------------------------- + // Test getAudioVolumeGroups and validate groud id + //----------------------------------------------------------------- + public void testGetVolumeGroups() throws Exception { + // Through AudioManager, the transaction behind getAudioVolumeGroups will succeed + final List<AudioVolumeGroup> audioVolumeGroup = mAudioManager.getAudioVolumeGroups(); + assertNotNull(audioVolumeGroup); + assertTrue(audioVolumeGroup.size() > 0); + + final List<AudioProductStrategy> audioProductStrategies = + mAudioManager.getAudioProductStrategies(); + assertTrue(audioProductStrategies.size() > 0); + + for (final AudioVolumeGroup avg : audioVolumeGroup) { + int avgId = avg.getId(); + assertNotEquals(avgId, AudioVolumeGroup.DEFAULT_VOLUME_GROUP); + + List<AudioAttributes> avgAttributes = avg.getAudioAttributes(); + assertNotNull(avgAttributes); + + final int[] avgStreamTypes = avg.getLegacyStreamTypes(); + assertNotNull(avgStreamTypes); + + // for each volume group attributes, find the matching product strategy and ensure + // it is linked the considered volume group + for (final AudioAttributes aa : avgAttributes) { + if (aa.equals(sDefaultAttributes)) { + // Some volume groups may not have valid attributes, used for internal + // volume management like patch/rerouting + // so bailing out strategy retrieval from attributes + continue; + } + boolean isVolumeGroupAssociatedToStrategy = false; + for (final AudioProductStrategy aps : audioProductStrategies) { + int groupId = aps.getVolumeGroupIdForAudioAttributes(aa); + if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + // Note that Audio Product Strategies are priority ordered, and the + // the first one matching the AudioAttributes will be used to identify + // the volume group associated to the request. + assertTrue(aps.supportsAudioAttributes(aa)); + assertEquals("Volume Group ID (" + avg.toString() + + "), and Volume group ID associated to Strategy (" + + aps.toString() + ") both supporting attributes " + + aa.toString() + " are mismatching", + avgId, groupId); + isVolumeGroupAssociatedToStrategy = true; + break; + } + } + assertTrue("Volume Group (" + avg.toString() + + ") has no associated strategy for attributes " + aa.toString(), + isVolumeGroupAssociatedToStrategy); + } + + // for each volume group stream type, find the matching product strategy and ensure + // it is linked the considered volume group + for (final int avgStreamType : avgStreamTypes) { + if (avgStreamType == AudioSystem.STREAM_DEFAULT) { + // Some Volume Groups may not have corresponding stream types as they + // intends to address volume setting per attributes to avoid adding new + // stream type and going on deprecating the stream type even for volume + // so bailing out strategy retrieval from stream type + continue; + } + boolean isVolumeGroupAssociatedToStrategy = false; + for (final AudioProductStrategy aps : audioProductStrategies) { + int groupId = aps.getVolumeGroupIdForLegacyStreamType(avgStreamType); + if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + + assertEquals("Volume Group ID (" + avg.toString() + + "), and Volume group ID associated to Strategy (" + + aps.toString() + ") both supporting stream " + + AudioSystem.streamToString(avgStreamType) + "(" + + avgStreamType + ") are mismatching", + avgId, groupId); + + isVolumeGroupAssociatedToStrategy = true; + break; + } + } + assertTrue("Volume Group (" + avg.toString() + + ") has no associated strategy for stream " + + AudioSystem.streamToString(avgStreamType) + "(" + avgStreamType + ")", + isVolumeGroupAssociatedToStrategy); + } + } + } +} diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java new file mode 100644 index 000000000000..a17d65cf7376 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.audiopolicytest; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.audiopolicy.AudioProductStrategy; +import android.media.audiopolicy.AudioVolumeGroup; +import android.test.ActivityInstrumentationTestCase2; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AudioVolumesTestBase extends ActivityInstrumentationTestCase2<AudioPolicyTest> { + public AudioManager mAudioManager; + Context mContext; + private Map<Integer, Integer> mOriginalStreamVolumes = new HashMap<>(); + private Map<Integer, Integer> mOriginalVolumeGroupVolumes = new HashMap<>(); + + // Default matches the invalid (empty) attributes from native. + // The difference is the input source default which is not aligned between native and java + public static final AudioAttributes sDefaultAttributes = + AudioProductStrategy.sDefaultAttributes; + + public static final AudioAttributes sInvalidAttributes = new AudioAttributes.Builder().build(); + + public final int[] PUBLIC_STREAM_TYPES = { AudioManager.STREAM_VOICE_CALL, + AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING, AudioManager.STREAM_MUSIC, + AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_DTMF, AudioManager.STREAM_ACCESSIBILITY }; + + public AudioVolumesTestBase() { + super("com.android.audiopolicytest", AudioPolicyTest.class); + } + + /** + * <p>Note: must be called with shell permission (MODIFY_AUDIO_ROUTING) + */ + private void storeAllVolumes() { + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + for (final AudioVolumeGroup avg : audioVolumeGroups) { + if (avg.getAudioAttributes().isEmpty()) { + // some volume group may not supports volume control per attributes + // like rerouting/patch since these groups are internal to audio policy manager + continue; + } + AudioAttributes avgAttributes = sDefaultAttributes; + for (final AudioAttributes aa : avg.getAudioAttributes()) { + if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) { + avgAttributes = aa; + break; + } + } + if (avgAttributes.equals(sDefaultAttributes)) { + // This shall not happen, however, not purpose of this base class. + // so bailing out. + continue; + } + mOriginalVolumeGroupVolumes.put( + avg.getId(), mAudioManager.getVolumeIndexForAttributes(avgAttributes)); + } + } + + /** + * <p>Note: must be called with shell permission (MODIFY_AUDIO_ROUTING) + */ + private void restoreAllVolumes() { + List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups(); + for (Map.Entry<Integer, Integer> e : mOriginalVolumeGroupVolumes.entrySet()) { + for (final AudioVolumeGroup avg : audioVolumeGroups) { + if (avg.getId() == e.getKey()) { + assertTrue(!avg.getAudioAttributes().isEmpty()); + AudioAttributes avgAttributes = sDefaultAttributes; + for (final AudioAttributes aa : avg.getAudioAttributes()) { + if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) { + avgAttributes = aa; + break; + } + } + assertTrue(!avgAttributes.equals(sDefaultAttributes)); + mAudioManager.setVolumeIndexForAttributes( + avgAttributes, e.getValue(), AudioManager.FLAG_ALLOW_RINGER_MODES); + } + } + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mContext = getActivity(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + + assertEquals(PackageManager.PERMISSION_GRANTED, + mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + + // Store the original volumes that that they can be recovered in tearDown(). + mOriginalStreamVolumes.clear(); + for (int streamType : PUBLIC_STREAM_TYPES) { + mOriginalStreamVolumes.put(streamType, mAudioManager.getStreamVolume(streamType)); + } + // Store the original volume per attributes so that they can be recovered in tearDown() + mOriginalVolumeGroupVolumes.clear(); + storeAllVolumes(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + // Recover the volume and the ringer mode that the test may have overwritten. + for (Map.Entry<Integer, Integer> e : mOriginalStreamVolumes.entrySet()) { + mAudioManager.setStreamVolume(e.getKey(), e.getValue(), + AudioManager.FLAG_ALLOW_RINGER_MODES); + } + + // Recover the original volume per attributes + restoreAllVolumes(); + } + + public static int resetVolumeIndex(int indexMin, int indexMax) { + return (indexMax + indexMin) / 2; + } + + public static int incrementVolumeIndex(int index, int indexMin, int indexMax) { + return (index + 1 > indexMax) ? resetVolumeIndex(indexMin, indexMax) : ++index; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 1814fd0403d0..a4bb916b16ea 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -26,6 +26,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import androidx.annotation.VisibleForTesting; + import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardViewController; @@ -332,6 +334,11 @@ public class CarKeyguardViewController extends OverlayViewController implements getLayout().setVisibility(View.INVISIBLE); } + @VisibleForTesting + void setKeyguardBouncer(KeyguardBouncer keyguardBouncer) { + mBouncer = keyguardBouncer; + } + private void revealKeyguardIfBouncerPrepared() { int reattemptDelayMillis = 50; Runnable revealKeyguard = () -> { diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index cd45fc908db4..d8111d04348b 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -77,7 +77,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -170,7 +169,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, - NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -251,7 +249,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt notificationInterruptStateProvider, notificationViewHierarchyManager, keyguardViewMediator, - notificationAlertingManager, displayMetrics, metricsLogger, uiBgExecutor, diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index e163173daefb..f72ab25a8028 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -63,7 +63,6 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -145,7 +144,6 @@ public class CarStatusBarModule { NotificationInterruptStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, - NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -225,7 +223,6 @@ public class CarStatusBarModule { notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, - notificationAlertingManager, displayMetrics, metricsLogger, uiBgExecutor, diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java new file mode 100644 index 000000000000..d4cf6ccf4b9e --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car.keyguard; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarServiceProvider; +import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.navigationbar.car.CarNavigationBarController; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.phone.BiometricUnlockController; +import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.window.OverlayViewGlobalStateController; +import com.android.systemui.window.SystemUIOverlayWindowController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class CarKeyguardViewControllerTest extends SysuiTestCase { + + private TestableCarKeyguardViewController mCarKeyguardViewController; + private OverlayViewGlobalStateController mOverlayViewGlobalStateController; + private ViewGroup mBaseLayout; + + @Mock + private KeyguardBouncer mBouncer; + @Mock + private CarNavigationBarController mCarNavigationBarController; + @Mock + private SystemUIOverlayWindowController mSystemUIOverlayWindowController; + @Mock + private CarKeyguardViewController.OnKeyguardCancelClickedListener mCancelClickedListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mOverlayViewGlobalStateController = new OverlayViewGlobalStateController( + mCarNavigationBarController, mSystemUIOverlayWindowController); + mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.sysui_overlay_window, /* root= */ null); + when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); + + mCarKeyguardViewController = new TestableCarKeyguardViewController( + mContext, + Handler.getMain(), + mock(CarServiceProvider.class), + mOverlayViewGlobalStateController, + mock(KeyguardStateController.class), + mock(KeyguardUpdateMonitor.class), + mock(BiometricUnlockController.class), + mock(ViewMediatorCallback.class), + mock(CarNavigationBarController.class), + mock(LockPatternUtils.class), + mock(DismissCallbackRegistry.class), + mock(FalsingManager.class), + mock(KeyguardBypassController.class) + ); + } + + @Test + public void onShow_bouncerIsSecure_showsBouncerWithSecuritySelectionReset() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + + verify(mBouncer).show(/* resetSecuritySelection= */ true); + } + + @Test + public void onShow_bouncerIsSecure_keyguardIsVisible() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + + assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo( + View.VISIBLE); + } + + @Test + public void onShow_bouncerNotSecure_hidesBouncerAndDestroysTheView() { + when(mBouncer.isSecure()).thenReturn(false); + mCarKeyguardViewController.show(/* options= */ null); + + verify(mBouncer).hide(/* destroyView= */ true); + } + + @Test + public void onShow_bouncerNotSecure_keyguardIsNotVisible() { + when(mBouncer.isSecure()).thenReturn(false); + mCarKeyguardViewController.show(/* options= */ null); + + assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo( + View.GONE); + } + + @Test + public void onHide_keyguardShowing_hidesBouncerAndDestroysTheView() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + mCarKeyguardViewController.hide(/* startTime= */ 0, /* fadeoutDelay= */ 0); + + verify(mBouncer).hide(/* destroyView= */ true); + } + + @Test + public void onHide_keyguardNotShown_doesNotHideOrDestroyBouncer() { + mCarKeyguardViewController.hide(/* startTime= */ 0, /* fadeoutDelay= */ 0); + + verify(mBouncer, never()).hide(anyBoolean()); + } + + @Test + public void onHide_KeyguardNotVisible() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + mCarKeyguardViewController.hide(/* startTime= */ 0, /* fadeoutDelay= */ 0); + + assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo( + View.GONE); + } + + @Test + public void onCancelClicked_callsCancelClickedListener() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + mCarKeyguardViewController.registerOnKeyguardCancelClickedListener(mCancelClickedListener); + mCarKeyguardViewController.onCancelClicked(); + + verify(mCancelClickedListener).onCancelClicked(); + } + + @Test + public void onCancelClicked_hidesBouncerAndDestroysTheView() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + mCarKeyguardViewController.registerOnKeyguardCancelClickedListener(mCancelClickedListener); + mCarKeyguardViewController.onCancelClicked(); + + verify(mBouncer).hide(/* destroyView= */ true); + } + + private class TestableCarKeyguardViewController extends CarKeyguardViewController { + + TestableCarKeyguardViewController(Context context, + Handler mainHandler, + CarServiceProvider carServiceProvider, + OverlayViewGlobalStateController overlayViewGlobalStateController, + KeyguardStateController keyguardStateController, + KeyguardUpdateMonitor keyguardUpdateMonitor, + BiometricUnlockController biometricUnlockController, + ViewMediatorCallback viewMediatorCallback, + CarNavigationBarController carNavigationBarController, + LockPatternUtils lockPatternUtils, + DismissCallbackRegistry dismissCallbackRegistry, + FalsingManager falsingManager, + KeyguardBypassController keyguardBypassController) { + super(context, mainHandler, carServiceProvider, overlayViewGlobalStateController, + keyguardStateController, keyguardUpdateMonitor, biometricUnlockController, + viewMediatorCallback, carNavigationBarController, lockPatternUtils, + dismissCallbackRegistry, falsingManager, keyguardBypassController); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + setKeyguardBouncer(CarKeyguardViewControllerTest.this.mBouncer); + } + } + +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index c11e1a03cb00..6fbee16e3dae 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -30,13 +30,17 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; +import android.os.UserManager; import android.permission.IPermissionManager; import android.util.Log; +import java.util.List; + /** * Select which activity is the first visible activity of the installation and forward the intent to * it. @@ -47,6 +51,7 @@ public class InstallStart extends Activity { private static final String DOWNLOADS_AUTHORITY = "downloads"; private IPackageManager mIPackageManager; private IPermissionManager mIPermissionManager; + private UserManager mUserManager; private boolean mAbortInstall = false; @Override @@ -54,6 +59,7 @@ public class InstallStart extends Activity { super.onCreate(savedInstanceState); mIPackageManager = AppGlobals.getPackageManager(); mIPermissionManager = AppGlobals.getPermissionManager(); + mUserManager = getSystemService(UserManager.class); Intent intent = getIntent(); String callingPackage = getCallingPackage(); @@ -144,13 +150,16 @@ public class InstallStart extends Activity { if (packages == null) { return false; } + final List<UserInfo> users = mUserManager.getUsers(); for (String packageName : packages) { - try { - if (uid == getPackageManager().getPackageUid(packageName, 0)) { - return true; + for (UserInfo user : users) { + try { + if (uid == getPackageManager().getPackageUidAsUser(packageName, user.id)) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // Ignore and try the next package } - } catch (PackageManager.NameNotFoundException e) { - // Ignore and try the next package } } } catch (RemoteException rexc) { diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java index fa2ec55bd81a..a77e34b4af1e 100644 --- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java @@ -147,6 +147,28 @@ public class RestrictedLockUtils { public EnforcedAdmin() { } + /** + * Combines two {@link EnforcedAdmin} into one: if one of them is null, then just return + * the other. If both of them are the same, then return that. Otherwise return the symbolic + * {@link #MULTIPLE_ENFORCED_ADMIN} + */ + public static EnforcedAdmin combine(EnforcedAdmin admin1, EnforcedAdmin admin2) { + if (admin1 == null) { + return admin2; + } + if (admin2 == null) { + return admin1; + } + if (admin1.equals(admin2)) { + return admin1; + } + if (!admin1.enforcedRestriction.equals(admin2.enforcedRestriction)) { + throw new IllegalArgumentException( + "Admins with different restriction cannot be combined"); + } + return MULTIPLE_ENFORCED_ADMIN; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/packages/SystemUI/res/anim/control_state_list_animator.xml b/packages/SystemUI/res/anim/control_state_list_animator.xml index 7940b8874ece..a20a9255f86f 100644 --- a/packages/SystemUI/res/anim/control_state_list_animator.xml +++ b/packages/SystemUI/res/anim/control_state_list_animator.xml @@ -18,11 +18,13 @@ <item android:state_pressed="true"> <set> <objectAnimator + android:interpolator="@interpolator/control_state" android:duration="50" android:propertyName="scaleX" android:valueTo="0.97" android:valueType="floatType" /> <objectAnimator + android:interpolator="@interpolator/control_state" android:duration="50" android:propertyName="scaleY" android:valueTo="0.97" @@ -33,11 +35,13 @@ <item> <set> <objectAnimator + android:interpolator="@interpolator/control_state" android:duration="250" android:propertyName="scaleX" android:valueTo="1" android:valueType="floatType" /> <objectAnimator + android:interpolator="@interpolator/control_state" android:duration="250" android:propertyName="scaleY" android:valueTo="1" diff --git a/packages/SystemUI/res/drawable-nodpi/star_outline.xml b/packages/SystemUI/res/drawable-nodpi/star_outline.xml deleted file mode 100644 index 13983c6fda8d..000000000000 --- a/packages/SystemUI/res/drawable-nodpi/star_outline.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<vector - xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M 11.99 6.491 L 13.15 9.377 L 13.713 10.776 L 15.148 10.902 L 18.103 11.167 L 15.854 13.221 L 14.766 14.216 L 15.089 15.703 L 15.759 18.74 L 13.222 17.127 L 11.99 16.321 L 10.758 17.102 L 8.222 18.715 L 8.891 15.678 L 9.215 14.191 L 8.126 13.196 L 5.877 11.141 L 8.832 10.877 L 10.267 10.751 L 10.83 9.352 L 11.99 6.491 M 11.99 0.027 L 8.628 8.382 L 0.027 9.15 L 6.559 15.111 L 4.597 23.97 L 11.99 19.27 L 19.383 23.97 L 17.421 15.111 L 23.953 9.15 L 15.352 8.382 Z" - android:fillColor="#FFFFFFFF"/> -</vector> diff --git a/packages/SystemUI/res/drawable/control_background.xml b/packages/SystemUI/res/drawable/control_background.xml index 29b4efa48fa1..cf298b70f3db 100644 --- a/packages/SystemUI/res/drawable/control_background.xml +++ b/packages/SystemUI/res/drawable/control_background.xml @@ -17,7 +17,8 @@ */ --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item> + <item + android:id="@+id/background"> <shape> <solid android:color="@color/control_default_background" /> <corners android:radius="@dimen/control_corner_radius" /> diff --git a/packages/SystemUI/res/drawable-nodpi/star_filled.xml b/packages/SystemUI/res/interpolator/control_state.xml index 62802d3cb838..66106d48d507 100644 --- a/packages/SystemUI/res/drawable-nodpi/star_filled.xml +++ b/packages/SystemUI/res/interpolator/control_state.xml @@ -14,14 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<vector +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M 11.99 0.027 L 8.628 8.382 L 0.027 9.15 L 6.559 15.111 L 4.597 23.97 L 11.99 19.27 L 19.383 23.97 L 17.421 15.111 L 23.953 9.15 L 15.352 8.382 Z" - android:fillColor="#FFFFFFFF"/> -</vector> + android:controlX1="0" + android:controlY1="0" + android:controlX2="1" + android:controlY2="1"/> diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 6a8621398191..fd75d91f0994 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -101,7 +101,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:button="@drawable/controls_btn_star" android:background="@android:color/transparent" android:clickable="false" android:selectable="false" diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index 9dc502efab43..f8a96e79d027 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -108,7 +108,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - style="@style/TextAppearance.NotificationImportanceChannel"/> + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> </LinearLayout> <TextView android:id="@+id/delegate_name" diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 5b363820e4e2..e8e0133103eb 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -84,7 +84,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - style="@style/TextAppearance.NotificationImportanceChannel"/> + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> </LinearLayout> <TextView android:id="@+id/delegate_name" diff --git a/packages/SystemUI/res/drawable-nodpi/controls_btn_star.xml b/packages/SystemUI/res/layout/photo_preview_overlay.xml index cfe783892b1d..9210996fb9c6 100644 --- a/packages/SystemUI/res/drawable-nodpi/controls_btn_star.xml +++ b/packages/SystemUI/res/layout/photo_preview_overlay.xml @@ -14,12 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp"> - <item android:state_checked="true" - android:drawable="@drawable/star_filled" - android:tint="@color/control_primary_text"/> - <item android:drawable="@drawable/star_outline" - android:tint="@color/control_primary_text"/> -</selector> + +<!-- empty stub --> +<merge />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml new file mode 100644 index 000000000000..ccb4f7832a62 --- /dev/null +++ b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml @@ -0,0 +1,194 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/onboarding_half_shell_container" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|bottom" + android:paddingStart="4dp" + android:paddingEnd="4dp" + > + + <LinearLayout + android:id="@+id/half_shell" + android:layout_width="@dimen/qs_panel_width" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="vertical" + android:gravity="bottom" + android:layout_gravity="center_horizontal|bottom" + android:background="@drawable/rounded_bg_full" + > + + <!-- We have a known number of rows that can be shown; just design them all here --> + <LinearLayout + android:id="@+id/show_at_top_tip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="4dp" + android:orientation="horizontal" + > + <ImageView + android:id="@+id/bell_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:src="@drawable/ic_notifications_alert" + android:tint="?android:attr/colorControlNormal" /> + + <TextView + android:id="@+id/show_at_top_text" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:gravity="center_vertical|start" + android:textSize="15sp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/priority_onboarding_show_at_top_text" + style="@style/TextAppearance.NotificationInfo" + /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/show_avatar_tip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="4dp" + android:orientation="horizontal" + > + <ImageView + android:id="@+id/avatar_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:src="@drawable/ic_person" + android:tint="?android:attr/colorControlNormal" /> + + <TextView + android:id="@+id/avatar_text" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:gravity="center_vertical|start" + android:textSize="15sp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/priority_onboarding_show_avatar_text" + style="@style/TextAppearance.NotificationInfo" + /> + + </LinearLayout> + + <!-- These rows show optionally --> + + <LinearLayout + android:id="@+id/floating_bubble_tip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="4dp" + android:orientation="horizontal" + > + + <ImageView + android:id="@+id/bubble_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:src="@drawable/ic_create_bubble" + android:tint="?android:attr/colorControlNormal" /> + + <TextView + android:id="@+id/bubble_text" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:gravity="center_vertical|start" + android:textSize="15sp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/priority_onboarding_appear_as_bubble_text" + style="@style/TextAppearance.NotificationInfo" + /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/ignore_dnd_tip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="4dp" + android:orientation="horizontal" + > + + <ImageView + android:id="@+id/dnd_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:src="@drawable/moon" + android:tint="?android:attr/colorControlNormal" /> + + <TextView + android:id="@+id/dnd_text" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:gravity="center_vertical|start" + android:textSize="15sp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/priority_onboarding_ignores_dnd_text" + style="@style/TextAppearance.NotificationInfo" + /> + + </LinearLayout> + + <!-- Bottom button container --> + <RelativeLayout + android:id="@+id/button_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="4dp" + android:orientation="horizontal" + > + <TextView + android:id="@+id/done_button" + android:text="@string/priority_onboarding_done_button_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:gravity="end|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + + </RelativeLayout> + + </LinearLayout> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 115b4a86b86d..1d4b98242519 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -66,6 +66,8 @@ <include layout="@layout/ambient_indication" android:id="@+id/ambient_indication_container" /> + <include layout="@layout/photo_preview_overlay" /> + <ViewStub android:id="@+id/keyguard_user_switcher" android:layout="@layout/keyguard_user_switcher" diff --git a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl index 716e1272f871..e4b6e0778664 100644 --- a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl +++ b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl @@ -3,74 +3,9 @@ precision mediump float; // The actual wallpaper texture. uniform sampler2D uTexture; -// The 85th percenile for the luminance histogram of the image (a value between 0 and 1). -// This value represents the point in histogram that includes 85% of the pixels of the image. -uniform float uPer85; - -// Reveal is the animation value that goes from 1 (the image is hidden) to 0 (the image is visible). -uniform float uReveal; - -// The opacity of locked screen (constant value). -uniform float uAod2Opacity; varying vec2 vTextureCoordinates; -/* - * Calculates the relative luminance of the pixel. - */ -vec3 luminosity(vec3 color) { - float lum = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; - return vec3(lum); -} - -vec4 transform(vec3 diffuse) { - // Getting the luminance for this pixel - vec3 lum = luminosity(diffuse); - - /* - * while the reveal > per85, it shows the luminance image (B&W image) - * then when moving passed that value, the image gets colored. - */ - float trans = smoothstep(0., uPer85, uReveal); - diffuse = mix(diffuse, lum, trans); - - // 'lower' value represents the capped 'reveal' value to the range [0, per85] - float selector = step(uPer85, uReveal); - float lower = mix(uReveal, uPer85, selector); - - /* - * Remaps image: - * - from reveal=1 to reveal=per85 => lower=per85, diffuse=luminance - * That means that remaps black and white image pixel - * from a possible values of [0,1] to [per85, 1] (if the pixel is darker than per85, - * it's gonna be black, if it's between per85 and 1, it's gonna be gray - * and if it's 1 it's gonna be white). - * - from reveal=per85 to reveal=0 => lower=reveal, 'diffuse' changes from luminance to color - * That means that remaps each image pixel color (rgb) - * from a possible values of [0,1] to [lower, 1] (if the pixel color is darker than 'lower', - * it's gonna be 0, if it's between 'lower' and 1, it's gonna be remap to a value - * between 0 and 1 and if it's 1 it's gonna be 1). - * - if reveal=0 => lower=0, diffuse=color image - * The image is shown as it is, colored. - */ - vec3 remaps = smoothstep(lower, 1., diffuse); - - // Interpolate between diffuse and remaps using reveal to avoid over saturation. - diffuse = mix(diffuse, remaps, uReveal); - - /* - * Fades in the pixel value: - * - if reveal=1 => fadeInOpacity=0 - * - from reveal=1 to reveal=per85 => 0<=fadeInOpacity<=1 - * - if reveal>per85 => fadeInOpacity=1 - */ - float fadeInOpacity = 1. - smoothstep(uPer85, 1., uReveal); - diffuse *= uAod2Opacity * fadeInOpacity; - - return vec4(diffuse.r, diffuse.g, diffuse.b, 1.); -} - void main() { // gets the pixel value of the wallpaper for this uv coordinates on screen. - vec4 fragColor = texture2D(uTexture, vTextureCoordinates); - gl_FragColor = transform(fragColor.rgb); + gl_FragColor = texture2D(uTexture, vTextureCoordinates); }
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e7ef8ccf4eba..599ed1696ec9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1252,6 +1252,8 @@ <dimen name="control_status_expanded">18sp</dimen> <dimen name="control_base_item_margin">2dp</dimen> <dimen name="control_status_padding">3dp</dimen> + <fraction name="controls_toggle_bg_intensity">5%</fraction> + <fraction name="controls_dimmed_alpha">40%</fraction> <!-- Home Controls activity view detail panel--> <dimen name="controls_activity_view_top_padding">25dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d2654d6d7d9a..cb20e7a15424 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1829,15 +1829,15 @@ <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary --> <string name="notification_channel_summary_default">Gets your attention with sound or vibration.</string> + <!-- [CHAR LIMIT=150] Conversation Notification Importance title: normal conversation level, with bubbling summary --> + <string name="notification_channel_summary_default_with_bubbles">Gets your attention with sound or vibration. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string> + <!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary --> <string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string> <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level summary --> <string name="notification_channel_summary_priority">Shows at top of conversation section and appears as a bubble.</string> - <!--[CHAR LIMIT=150] Conversation inline controls footer shown when all conversations from the app are allowed to show as bubbles --> - <string name="notification_conversation_channel_all_bubble">All conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default. Manage in <xliff:g id="app_name" example="Settings">%2$s</xliff:g>.</string> - <!--[CHAR LIMIT=30] Linkable text to Settings app --> <string name="notification_conversation_channel_settings">Settings</string> @@ -2639,6 +2639,18 @@ <!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] --> <string name="inattentive_sleep_warning_title">Standby</string> + <!-- Priority conversation onboarding screen --> + <!-- Text explaining that priority conversations show at the top of the conversation section [CHAR LIMIT=50] --> + <string name="priority_onboarding_show_at_top_text">Show at top of conversation section</string> + <!-- Text explaining that priority conversations show an avatar on the lock screen [CHAR LIMIT=50] --> + <string name="priority_onboarding_show_avatar_text">Show profile picture on lock screen</string> + <!-- Text explaining that priority conversations will appear as a bubble [CHAR LIMIT=50] --> + <string name="priority_onboarding_appear_as_bubble_text">Appear as a floating bubble on top of apps</string> + <!-- Text explaining that priority conversations can interrupt DnD settings [CHAR LIMIT=50] --> + <string name="priority_onboarding_ignores_dnd_text">Interrupt Do Not Disturb</string> + <!-- Title for the affirmative button [CHAR LIMIT=50] --> + <string name="priority_onboarding_done_button_title">Got it</string> + <!-- Window Magnification strings --> <!-- Title for Magnification Overlay Window [CHAR LIMIT=NONE] --> <string name="magnification_overlay_title">Magnification Overlay Window</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 118aa5b3f96a..7e24f5dbbd50 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -564,7 +564,7 @@ <style name="TextAppearance.NotificationImportanceButton"> <item name="android:textSize">@dimen/notification_importance_button_text</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> - <item name="android:textColor">?android:attr/colorAccent</item> + <item name="android:textColor">@color/notification_guts_priority_contents</item> <item name="android:gravity">center</item> </style> diff --git a/packages/SystemUI/scripts/update_statsd_lib.sh b/packages/SystemUI/scripts/update_statsd_lib.sh new file mode 100755 index 000000000000..79b9497a5f3f --- /dev/null +++ b/packages/SystemUI/scripts/update_statsd_lib.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +NUM_ARGS=$# +JAR_DESTINATION="$1/prebuilts/framework_intermediates/libs/systemui_statsd.jar" + +has_croot() { + declare -F croot > /dev/null + return $? +} + +check_environment() { + if ! has_croot; then + echo "Run script in a shell that has had envsetup run. Run '. update_statsd_lib.sh' from scripts directory" + return 1 + fi + + if [ $NUM_ARGS -ne 1 ]; then + echo "Usage: . update_statsd_lib.sh PATH_TO_UNBUNDLED_LAUNCER e.g. . update_statsd_lib ~/src/ub-launcher3-master" + return 1 + fi + return 0 +} + +main() { + if check_environment ; then + pushd . + croot + mma -j16 SystemUI-statsd + cp out/target/product/$TARGET_PRODUCT/obj/JAVA_LIBRARIES/SystemUI-statsd_intermediates/javalib.jar $JAR_DESTINATION + popd + fi +} + +main + diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index b6152dae33d6..0af026eb3509 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -75,7 +75,6 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; @@ -293,8 +292,6 @@ public class Dependency { @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; @Inject Lazy<BubbleController> mBubbleController; @Inject Lazy<NotificationEntryManager> mNotificationEntryManager; - @Inject - Lazy<NotificationAlertingManager> mNotificationAlertingManager; @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; @Inject Lazy<AutoHideController> mAutoHideController; @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener; @@ -493,7 +490,6 @@ public class Dependency { mRemoteInputQuickSettingsDisabler::get); mProviders.put(BubbleController.class, mBubbleController::get); mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get); - mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get); mProviders.put(ForegroundServiceNotificationListener.class, mForegroundServiceNotificationListener::get); mProviders.put(ClockManager.class, mClockManager::get); diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 5442299881c0..71ec33e16e0e 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -16,9 +16,6 @@ package com.android.systemui; -import android.app.ActivityManager; -import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Handler; import android.os.HandlerThread; @@ -27,17 +24,12 @@ import android.os.Trace; import android.service.wallpaper.WallpaperService; import android.util.Log; import android.util.Size; -import android.view.DisplayInfo; import android.view.SurfaceHolder; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.glwallpaper.EglHelper; import com.android.systemui.glwallpaper.GLWallpaperRenderer; import com.android.systemui.glwallpaper.ImageWallpaperRenderer; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.phone.DozeParameters; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -53,16 +45,12 @@ public class ImageWallpaper extends WallpaperService { // We delayed destroy render context that subsequent render requests have chance to cancel it. // This is to avoid destroying then recreating render context in a very short time. private static final int DELAY_FINISH_RENDERING = 1000; - private static final int INTERVAL_WAIT_FOR_RENDERING = 100; - private static final int PATIENCE_WAIT_FOR_RENDERING = 10; - private static final boolean DEBUG = true; - private final DozeParameters mDozeParameters; + private static final boolean DEBUG = false; private HandlerThread mWorker; @Inject - public ImageWallpaper(DozeParameters dozeParameters) { + public ImageWallpaper() { super(); - mDozeParameters = dozeParameters; } @Override @@ -74,7 +62,7 @@ public class ImageWallpaper extends WallpaperService { @Override public Engine onCreateEngine() { - return new GLEngine(this, mDozeParameters); + return new GLEngine(); } @Override @@ -84,7 +72,7 @@ public class ImageWallpaper extends WallpaperService { mWorker = null; } - class GLEngine extends Engine implements GLWallpaperRenderer.SurfaceProxy, StateListener { + class GLEngine extends Engine { // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin) // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail. @VisibleForTesting @@ -94,40 +82,15 @@ public class ImageWallpaper extends WallpaperService { private GLWallpaperRenderer mRenderer; private EglHelper mEglHelper; - private StatusBarStateController mController; private final Runnable mFinishRenderingTask = this::finishRendering; - private boolean mShouldStopTransition; - private final DisplayInfo mDisplayInfo = new DisplayInfo(); - private final Object mMonitor = new Object(); - @VisibleForTesting - boolean mIsHighEndGfx; - private boolean mDisplayNeedsBlanking; - private boolean mNeedTransition; private boolean mNeedRedraw; - // This variable can only be accessed in synchronized block. - private boolean mWaitingForRendering; - GLEngine(Context context, DozeParameters dozeParameters) { - init(dozeParameters); + GLEngine() { } @VisibleForTesting - GLEngine(DozeParameters dozeParameters, Handler handler) { + GLEngine(Handler handler) { super(SystemClock::elapsedRealtime, handler); - init(dozeParameters); - } - - private void init(DozeParameters dozeParameters) { - mIsHighEndGfx = ActivityManager.isHighEndGfx(); - mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking(); - mNeedTransition = false; - - // We will preserve EGL context when we are in lock screen or aod - // to avoid janking in following transition, we need to release when back to home. - mController = Dependency.get(StatusBarStateController.class); - if (mController != null) { - mController.addCallback(this /* StateListener */); - } } @Override @@ -135,9 +98,8 @@ public class ImageWallpaper extends WallpaperService { mEglHelper = getEglHelperInstance(); // Deferred init renderer because we need to get wallpaper by display context. mRenderer = getRendererInstance(); - getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo); setFixedSizeAllowed(true); - setOffsetNotificationsEnabled(mNeedTransition); + setOffsetNotificationsEnabled(false); updateSurfaceSize(); } @@ -146,7 +108,7 @@ public class ImageWallpaper extends WallpaperService { } ImageWallpaperRenderer getRendererInstance() { - return new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */); + return new ImageWallpaperRenderer(getDisplayContext()); } private void updateSurfaceSize() { @@ -157,79 +119,13 @@ public class ImageWallpaper extends WallpaperService { holder.setFixedSize(width, height); } - /** - * Check if necessary to stop transition with current wallpaper on this device. <br/> - * This should only be invoked after {@link #onSurfaceCreated(SurfaceHolder)}} - * is invoked since it needs display context and surface frame size. - * @return true if need to stop transition. - */ - @VisibleForTesting - boolean checkIfShouldStopTransition() { - int orientation = getDisplayContext().getResources().getConfiguration().orientation; - Rect frame = getSurfaceHolder().getSurfaceFrame(); - Rect display = new Rect(); - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - display.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); - } else { - display.set(0, 0, mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth); - } - return mNeedTransition - && (frame.width() < display.width() || frame.height() < display.height()); - } - - @Override - public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, - float yOffsetStep, int xPixelOffset, int yPixelOffset) { - if (mWorker == null) return; - mWorker.getThreadHandler().post(() -> mRenderer.updateOffsets(xOffset, yOffset)); - } - - @Override - public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) { - if (mWorker == null || !mNeedTransition) return; - final long duration = mShouldStopTransition ? 0 : animationDuration; - if (DEBUG) { - Log.d(TAG, "onAmbientModeChanged: inAmbient=" + inAmbientMode - + ", duration=" + duration - + ", mShouldStopTransition=" + mShouldStopTransition); - } - mWorker.getThreadHandler().post( - () -> mRenderer.updateAmbientMode(inAmbientMode, duration)); - if (inAmbientMode && animationDuration == 0) { - // This means that we are transiting from home to aod, to avoid - // race condition between window visibility and transition, - // we don't return until the transition is finished. See b/136643341. - waitForBackgroundRendering(); - } - } - @Override public boolean shouldZoomOutWallpaper() { return true; } - private void waitForBackgroundRendering() { - synchronized (mMonitor) { - try { - mWaitingForRendering = true; - for (int patience = 1; mWaitingForRendering; patience++) { - mMonitor.wait(INTERVAL_WAIT_FOR_RENDERING); - mWaitingForRendering &= patience < PATIENCE_WAIT_FOR_RENDERING; - } - } catch (InterruptedException ex) { - } finally { - mWaitingForRendering = false; - } - } - } - @Override public void onDestroy() { - if (mController != null) { - mController.removeCallback(this /* StateListener */); - } - mController = null; - mWorker.getThreadHandler().post(() -> { mRenderer.finish(); mRenderer = null; @@ -240,7 +136,6 @@ public class ImageWallpaper extends WallpaperService { @Override public void onSurfaceCreated(SurfaceHolder holder) { - mShouldStopTransition = checkIfShouldStopTransition(); if (mWorker == null) return; mWorker.getThreadHandler().post(() -> { mEglHelper.init(holder, needSupportWideColorGamut()); @@ -251,32 +146,13 @@ public class ImageWallpaper extends WallpaperService { @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mWorker == null) return; - mWorker.getThreadHandler().post(() -> { - mRenderer.onSurfaceChanged(width, height); - mNeedRedraw = true; - }); + mWorker.getThreadHandler().post(() -> mRenderer.onSurfaceChanged(width, height)); } @Override public void onSurfaceRedrawNeeded(SurfaceHolder holder) { if (mWorker == null) return; - if (DEBUG) { - Log.d(TAG, "onSurfaceRedrawNeeded: mNeedRedraw=" + mNeedRedraw); - } - - mWorker.getThreadHandler().post(() -> { - if (mNeedRedraw) { - drawFrame(); - mNeedRedraw = false; - } - }); - } - - @Override - public void onVisibilityChanged(boolean visible) { - if (DEBUG) { - Log.d(TAG, "wallpaper visibility changes: " + visible); - } + mWorker.getThreadHandler().post(this::drawFrame); } private void drawFrame() { @@ -285,15 +161,6 @@ public class ImageWallpaper extends WallpaperService { postRender(); } - @Override - public void onStatePostChange() { - // When back to home, we try to release EGL, which is preserved in lock screen or aod. - if (mWorker != null && mController.getState() == StatusBarState.SHADE) { - mWorker.getThreadHandler().post(this::scheduleFinishRendering); - } - } - - @Override public void preRender() { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#preRender"); @@ -330,7 +197,6 @@ public class ImageWallpaper extends WallpaperService { } } - @Override public void requestRender() { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#requestRender"); @@ -355,27 +221,13 @@ public class ImageWallpaper extends WallpaperService { } } - @Override public void postRender() { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#postRender"); - notifyWaitingThread(); scheduleFinishRendering(); Trace.endSection(); } - private void notifyWaitingThread() { - synchronized (mMonitor) { - if (mWaitingForRendering) { - try { - mWaitingForRendering = false; - mMonitor.notify(); - } catch (IllegalMonitorStateException ex) { - } - } - } - } - private void cancelFinishRenderingTask() { if (mWorker == null) return; mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask); @@ -391,18 +243,11 @@ public class ImageWallpaper extends WallpaperService { Trace.beginSection("ImageWallpaper#finishRendering"); if (mEglHelper != null) { mEglHelper.destroyEglSurface(); - if (!needPreserveEglContext()) { - mEglHelper.destroyEglContext(); - } + mEglHelper.destroyEglContext(); } Trace.endSection(); } - private boolean needPreserveEglContext() { - return mNeedTransition && mController != null - && mController.getState() == StatusBarState.KEYGUARD; - } - private boolean needSupportWideColorGamut() { return mRenderer.isWcgContent(); } @@ -411,16 +256,6 @@ public class ImageWallpaper extends WallpaperService { protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { super.dump(prefix, fd, out, args); out.print(prefix); out.print("Engine="); out.println(this); - out.print(prefix); out.print("isHighEndGfx="); out.println(mIsHighEndGfx); - out.print(prefix); out.print("displayNeedsBlanking="); - out.println(mDisplayNeedsBlanking); - out.print(prefix); out.print("displayInfo="); out.print(mDisplayInfo); - out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition); - out.print(prefix); out.print("mShouldStopTransition="); - out.println(mShouldStopTransition); - out.print(prefix); out.print("StatusBarState="); - out.println(mController != null ? mController.getState() : "null"); - out.print(prefix); out.print("valid surface="); out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null ? getSurfaceHolder().getSurface().isValid() diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java index 6923079dd5c4..2eba9521b9e7 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -54,6 +54,11 @@ public class Interpolators { public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f, 1); public static final Interpolator BOUNCE = new BounceInterpolator(); + /** + * For state transitions on the control panel that lives in GlobalActions. + */ + public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f, + 1.0f); /** * Interpolator to be used when animating a move based on a click. Pair with enough duration. diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 6aa2326c388a..87990cd3bffa 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -21,11 +21,25 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import com.android.systemui.settings.CurrentUserContextTracker; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; import java.util.Set; +/** + * A helper class to store simple preferences for SystemUI. Its main use case is things such as + * feature education, e.g. "has the user seen this tooltip". + * + * As of this writing, feature education settings are *intentionally exempted* from backup and + * restore because there is not a great way to know which subset of features the user _should_ see + * again if, for instance, they are coming from multiple OSes back or switching OEMs. + * + * NOTE: Clients of this class should take care to pass in the correct user context when querying + * settings, otherwise you will always read/write for user 0 which is almost never what you want. + * See {@link CurrentUserContextTracker} for a simple way to get the current context + */ public final class Prefs { private Prefs() {} // no instantation @@ -109,6 +123,8 @@ public final class Prefs { String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding"; String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; + /** Tracks whether the user has seen the onboarding screen for priority conversations */ + String HAS_SEEN_PRIORITY_ONBOARDING = "HasSeenPriorityOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 8df3dd2ad845..7861211e802d 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -54,8 +54,10 @@ import android.graphics.Region; import android.graphics.drawable.VectorDrawable; import android.hardware.display.DisplayManager; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings.Secure; import android.util.DisplayMetrics; import android.util.Log; @@ -298,13 +300,15 @@ public class ScreenDecorations extends SystemUI implements Tunable { updateColorInversion(value); } }; + + mColorInversionSetting.setListening(true); + mColorInversionSetting.onChange(false); } - mColorInversionSetting.setListening(true); - mColorInversionSetting.onChange(false); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); + mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter, + new HandlerExecutor(mHandler), UserHandle.ALL); mIsRegistered = true; } else { mMainHandler.post(() -> mTunerService.removeTunable(this)); @@ -313,7 +317,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { mColorInversionSetting.setListening(false); } - mBroadcastDispatcher.unregisterReceiver(mIntentReceiver); + mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver); mIsRegistered = false; } } @@ -503,17 +507,16 @@ public class ScreenDecorations extends SystemUI implements Tunable { } } - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Intent.ACTION_USER_SWITCHED)) { - int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - ActivityManager.getCurrentUser()); - // update color inversion setting to the new user - mColorInversionSetting.setUserId(newUserId); - updateColorInversion(mColorInversionSetting.getValue()); + int newUserId = ActivityManager.getCurrentUser(); + if (DEBUG) { + Log.d(TAG, "UserSwitched newUserId=" + newUserId); } + // update color inversion setting to the new user + mColorInversionSetting.setUserId(newUserId); + updateColorInversion(mColorInversionSetting.getValue()); } }; @@ -945,7 +948,12 @@ public class ScreenDecorations extends SystemUI implements Tunable { int dw = flipped ? lh : lw; int dh = flipped ? lw : lh; - mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh)); + Path path = DisplayCutout.pathFromResources(getResources(), dw, dh); + if (path != null) { + mBoundingPath.set(path); + } else { + mBoundingPath.reset(); + } Matrix m = new Matrix(); transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m); mBoundingPath.transform(m); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java index d8a11d36a335..e6a62c26712a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.os.UserHandle; import android.text.InputType; import android.util.AttributeSet; import android.view.KeyEvent; @@ -68,6 +69,7 @@ public class AuthCredentialPasswordView extends AuthCredentialView protected void onAttachedToWindow() { super.onAttachedToWindow(); + mPasswordField.setTextOperationUser(UserHandle.of(mUserId)); if (mCredentialType == Utils.CREDENTIAL_PIN) { mPasswordField.setInputType( InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 8bf259182544..496e60ddf99e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -286,6 +286,7 @@ public abstract class AuthCredentialView extends LinearLayout { if (matched) { mClearErrorRunnable.run(); + mLockPatternUtils.userPresent(mEffectiveUserId); mCallback.onCredentialMatched(attestation); } else { if (timeoutMs > 0) { diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 74b94e76dfc1..319a6e09d4d1 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -25,7 +25,6 @@ import android.os.Looper import android.os.Message import android.os.UserHandle import android.text.TextUtils -import android.util.Log import android.util.SparseArray import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable @@ -34,6 +33,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import java.io.FileDescriptor import java.io.PrintWriter +import java.lang.IllegalStateException import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @@ -189,8 +189,8 @@ open class BroadcastDispatcher @Inject constructor ( data.user.identifier } if (userId < UserHandle.USER_ALL) { - if (DEBUG) Log.w(TAG, "Register receiver for invalid user: $userId") - return + throw IllegalStateException( + "Attempting to register receiver for invalid user {$userId}") } val uBR = receiversByUser.get(userId, createUBRForUser(userId)) receiversByUser.put(userId, uBR) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index da5c2968c6ac..c8e9a687ad23 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -108,7 +108,6 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; /** @@ -162,14 +161,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Used when ranking updates occur and we check if things should bubble / unbubble private NotificationListenerService.Ranking mTmpRanking; - // Saves notification keys of user created "fake" bubbles so that we can allow notifications - // like these to bubble by default. Doesn't persist across reboots, not a long-term solution. - private final HashSet<String> mUserCreatedBubbles; - // If we're auto-bubbling bubbles via a whitelist, we need to track which notifs from that app - // have been "demoted" back to a notification so that we don't auto-bubbles those again. - // Doesn't persist across reboots, not a long-term solution. - private final HashSet<String> mUserBlockedBubbles; - // Bubbles get added to the status bar view private final NotificationShadeWindowController mNotificationShadeWindowController; private final ZenModeController mZenModeController; @@ -412,9 +403,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } }); - mUserCreatedBubbles = new HashSet<>(); - mUserBlockedBubbles = new HashSet<>(); - mBubbleIconFactory = new BubbleIconFactory(context); } @@ -474,8 +462,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi (entry != null && entry.isRowDismissed() && !isAppCancel) || isClearAll || isUserDimiss || isSummaryCancel; - if (userRemovedNotif || isUserCreatedBubble(key) - || isSummaryOfUserCreatedBubble(entry)) { + if (userRemovedNotif) { return handleDismissalInterception(entry); } @@ -860,27 +847,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Whether this bubble was explicitly created by the user via a SysUI affordance. - */ - boolean isUserCreatedBubble(String key) { - return mUserCreatedBubbles.contains(key); - } - - boolean isSummaryOfUserCreatedBubble(NotificationEntry entry) { - if (isSummaryOfBubbles(entry)) { - List<Bubble> bubbleChildren = - mBubbleData.getBubblesInGroup(entry.getSbn().getGroupKey()); - for (int i = 0; i < bubbleChildren.size(); i++) { - // Check if any are user-created (i.e. experimental bubbles) - if (isUserCreatedBubble(bubbleChildren.get(i).getKey())) { - return true; - } - } - } - return false; - } - - /** * Removes the bubble with the given NotificationEntry. * <p> * Must be called from the main thread. @@ -893,37 +859,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } private void onEntryAdded(NotificationEntry entry) { - boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); - boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); - boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( - mContext, entry, previouslyUserCreated, userBlocked); - if (mNotificationInterruptStateProvider.shouldBubbleUp(entry) - && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) { - if (wasAdjusted && !previouslyUserCreated) { - // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated - mUserCreatedBubbles.add(entry.getKey()); - } + && canLaunchInActivityView(mContext, entry)) { updateBubble(entry); } } private void onEntryUpdated(NotificationEntry entry) { - boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); - boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); - boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( - mContext, entry, previouslyUserCreated, userBlocked); - boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry) - && (canLaunchInActivityView(mContext, entry) || wasAdjusted); + && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry, DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble) { - if (wasAdjusted && !previouslyUserCreated) { - // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated - mUserCreatedBubbles.add(entry.getKey()); - } updateBubble(entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 93fb6972fad5..3524696dbc79 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -193,7 +193,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList + " mActivityViewStatus=" + mActivityViewStatus + " bubble=" + getBubbleKey()); } - if (mBubble != null && !mBubbleController.isUserCreatedBubble(mBubble.getKey())) { + if (mBubble != null) { // Must post because this is called from a binder thread. post(() -> mBubbleController.removeBubble(mBubble.getEntry(), BubbleController.DISMISS_TASK_FINISHED)); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java index 41dbb489c2f6..2060391d38a3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java @@ -57,16 +57,13 @@ import java.util.List; public class BubbleExperimentConfig { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; - private static final String SHORTCUT_DUMMY_INTENT = "bubble_experiment_shortcut_intent"; - private static PendingIntent sDummyShortcutIntent; - private static final int BUBBLE_HEIGHT = 10000; private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble"; private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false; private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble"; - private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = true; + private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false; private static final String ALLOW_SHORTCUTS_TO_BUBBLE = "allow_shortcuts_to_bubble"; private static final boolean ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT = false; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 1cabe221bef1..0aabdff96e1e 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -374,8 +374,9 @@ public class BubbleStackView extends FrameLayout { @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { mExpandedAnimationController.dismissDraggedOutBubble( - mExpandedAnimationController.getDraggedOutBubble(), - BubbleStackView.this::dismissMagnetizedObject); + mExpandedAnimationController.getDraggedOutBubble() /* bubble */, + mDismissTargetContainer.getHeight() /* translationYBy */, + BubbleStackView.this::dismissMagnetizedObject /* after */); hideDismissTarget(); } }; @@ -405,7 +406,8 @@ public class BubbleStackView extends FrameLayout { @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mStackAnimationController.implodeStack( + mStackAnimationController.animateStackDismissal( + mDismissTargetContainer.getHeight() /* translationYBy */, () -> { resetDesaturationAndDarken(); dismissMagnetizedObject(); @@ -954,6 +956,8 @@ public class BubbleStackView extends FrameLayout { mVerticalPosPercentBeforeRotation = (mStackAnimationController.getStackPosition().y - allowablePos.top) / (allowablePos.bottom - allowablePos.top); + mVerticalPosPercentBeforeRotation = + Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation)); addOnLayoutChangeListener(mOrientationChangedListener); hideFlyoutImmediate(); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 501e5024d940..c96f9a470ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -139,22 +139,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask StatusBarNotification sbn = b.getEntry().getSbn(); String packageName = sbn.getPackageName(); - // Real shortcut info for this bubble String bubbleShortcutId = b.getEntry().getBubbleMetadata().getShortcutId(); if (bubbleShortcutId != null) { - info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c, packageName, - sbn.getUser(), bubbleShortcutId); - } else { - // Check for experimental shortcut - String shortcutId = sbn.getNotification().getShortcutId(); - if (BubbleExperimentConfig.useShortcutInfoToBubble(c) && shortcutId != null) { - info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c, - packageName, - sbn.getUser(), shortcutId); - } + info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo(); } - // App name & app icon PackageManager pm = c.getPackageManager(); ApplicationInfo appInfo; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index d974adc34ee0..0002e862bb41 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -329,7 +329,7 @@ public class ExpandedAnimationController } /** Plays a dismiss animation on the dragged out bubble. */ - public void dismissDraggedOutBubble(View bubble, Runnable after) { + public void dismissDraggedOutBubble(View bubble, float translationYBy, Runnable after) { if (bubble == null) { return; } @@ -337,6 +337,7 @@ public class ExpandedAnimationController .withStiffness(SpringForce.STIFFNESS_HIGH) .scaleX(1.1f) .scaleY(1.1f) + .translationY(bubble.getTranslationY() + translationYBy) .alpha(0f, after) .start(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index 00de8b4a51b8..5f3a2bd9eb8b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -647,17 +647,18 @@ public class StackAnimationController extends } /** - * 'Implode' the stack by shrinking the bubbles via chained animations and fading them out. + * 'Implode' the stack by shrinking the bubbles, fading them out, and translating them down. */ - public void implodeStack(Runnable after) { - // Pop and fade the bubbles sequentially. - animationForChildAtIndex(0) - .scaleX(0.5f) - .scaleY(0.5f) - .alpha(0f) - .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) - .withStiffness(SpringForce.STIFFNESS_HIGH) - .start(after); + public void animateStackDismissal(float translationYBy, Runnable after) { + animationsForChildrenFromIndex(0, (index, animation) -> + animation + .scaleX(0.5f) + .scaleY(0.5f) + .alpha(0f) + .translationY( + mLayout.getChildAt(index).getTranslationY() + translationYBy) + .withStiffness(SpringForce.STIFFNESS_HIGH)) + .startAll(after); } /** @@ -710,8 +711,6 @@ public class StackAnimationController extends if (property.equals(DynamicAnimation.TRANSLATION_X) || property.equals(DynamicAnimation.TRANSLATION_Y)) { return index + 1; - } else if (isStackStuckToTarget()) { - return index + 1; // Chain all animations in dismiss (scale, alpha, etc. are used). } else { return NONE; } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 7cab847d52f7..79a5b0a1ce52 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -23,6 +23,7 @@ import android.service.controls.actions.ControlAction import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.UserAwareController import com.android.systemui.controls.management.ControlsFavoritingActivity +import com.android.systemui.controls.ui.ControlWithState import com.android.systemui.controls.ui.ControlsUiController import java.util.function.Consumer @@ -111,6 +112,13 @@ interface ControlsController : UserAwareController { @ControlAction.ResponseResult response: Int ) + /** + * When a control should be highlighted, dimming down what's around it. + * + * @param cws focused control, or {@code null} if nothing should be highlighted. + */ + fun onFocusChanged(cws: ControlWithState?) + // FAVORITE MANAGEMENT /** diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 6d34009169d5..5626a5de2e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -41,6 +41,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.ui.ControlWithState import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager @@ -504,6 +505,10 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun onFocusChanged(cws: ControlWithState?) { + uiController.onFocusChanged(cws) + } + override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt index ad86eeb089c8..b3c6cab2adff 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt @@ -24,12 +24,12 @@ import android.service.controls.actions.BooleanAction import android.service.controls.actions.CommandAction import android.util.Log import android.view.HapticFeedbackConstants - import com.android.systemui.R +import com.android.systemui.controls.controller.ControlsController object ControlActionCoordinator { - public const val MIN_LEVEL = 0 - public const val MAX_LEVEL = 10000 + const val MIN_LEVEL = 0 + const val MAX_LEVEL = 10000 private var dialog: Dialog? = null @@ -40,9 +40,6 @@ object ControlActionCoordinator { fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { cvh.action(BooleanAction(templateId, !isChecked)) - - val nextLevel = if (isChecked) MIN_LEVEL else MAX_LEVEL - cvh.clipLayer.setLevel(nextLevel) } fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { @@ -80,4 +77,8 @@ object ControlActionCoordinator { it.show() } } + + fun setFocusedElement(cvh: ControlViewHolder?, controlsController: ControlsController) { + controlsController.onFocusChanged(cvh?.cws) + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 0eb6cb100933..61a323d0b9a8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -16,6 +16,9 @@ package com.android.systemui.controls.ui +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator import android.content.Context import android.graphics.drawable.ClipDrawable import android.graphics.drawable.GradientDrawable @@ -28,15 +31,16 @@ import android.service.controls.templates.StatelessTemplate import android.service.controls.templates.TemperatureControlTemplate import android.service.controls.templates.ToggleRangeTemplate import android.service.controls.templates.ToggleTemplate +import android.util.MathUtils import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView - +import com.android.internal.graphics.ColorUtils +import com.android.systemui.Interpolators +import com.android.systemui.R import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.R - import kotlin.reflect.KClass /** @@ -53,15 +57,22 @@ class ControlViewHolder( ) { companion object { + const val STATE_ANIMATION_DURATION = 700L private const val UPDATE_DELAY_IN_MILLIS = 3000L private const val ALPHA_ENABLED = (255.0 * 0.2).toInt() - private const val ALPHA_DISABLED = 255 + private const val ALPHA_DISABLED = 0 private val FORCE_PANEL_DEVICES = setOf( DeviceTypes.TYPE_THERMOSTAT, DeviceTypes.TYPE_CAMERA ) } + private val toggleBackgroundIntensity: Float = layout.context.resources + .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) + private val dimmedAlpha: Float = layout.context.resources + .getFraction(R.fraction.controls_dimmed_alpha, 1, 1) + private var stateAnimator: ValueAnimator? = null + private val baseLayer: GradientDrawable val icon: ImageView = layout.requireViewById(R.id.icon) val status: TextView = layout.requireViewById(R.id.status) val title: TextView = layout.requireViewById(R.id.title) @@ -74,11 +85,18 @@ class ControlViewHolder( var lastAction: ControlAction? = null val deviceType: Int get() = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType + var dimmed: Boolean = false + set(value) { + field = value + bindData(cws) + } init { val ld = layout.getBackground() as LayerDrawable ld.mutate() clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable + clipLayer.alpha = ALPHA_DISABLED + baseLayer = ld.findDrawableByLayerId(R.id.background) as GradientDrawable // needed for marquee to start status.setSelected(true) } @@ -160,30 +178,63 @@ class ControlViewHolder( } } - internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0) { + internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0, animated: Boolean = true) { setEnabled(enabled) val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset) - val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) - val (bg, alpha) = if (enabled) { - Pair(ri.enabledBackground, ALPHA_ENABLED) + val fg = context.resources.getColorStateList(ri.foreground, context.theme) + val bg = context.resources.getColor(R.color.control_default_background, context.theme) + val dimAlpha = if (dimmed) dimmedAlpha else 1f + var (clip, newAlpha) = if (enabled) { + listOf(ri.enabledBackground, ALPHA_ENABLED) } else { - Pair(R.color.control_default_background, ALPHA_DISABLED) + listOf(R.color.control_default_background, ALPHA_DISABLED) } status.setTextColor(fg) - icon.setImageDrawable(ri.icon) // do not color app icons if (deviceType != DeviceTypes.TYPE_ROUTINE) { - icon.setImageTintList(fg) + icon.imageTintList = fg } (clipLayer.getDrawable() as GradientDrawable).apply { - setColor(context.getResources().getColor(bg, context.getTheme())) - setAlpha(alpha) + val newClipColor = context.resources.getColor(clip, context.theme) + val newBaseColor = if (behavior is ToggleRangeBehavior) { + ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity) + } else { + bg + } + stateAnimator?.cancel() + if (animated) { + val oldColor = color?.defaultColor ?: newClipColor + val oldBaseColor = baseLayer.color?.defaultColor ?: newBaseColor + val oldAlpha = layout.alpha + stateAnimator = ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply { + addUpdateListener { + alpha = it.animatedValue as Int + setColor(ColorUtils.blendARGB(oldColor, newClipColor, it.animatedFraction)) + baseLayer.setColor(ColorUtils.blendARGB(oldBaseColor, + newBaseColor, it.animatedFraction)) + layout.alpha = MathUtils.lerp(oldAlpha, dimAlpha, it.animatedFraction) + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + stateAnimator = null + } + }) + duration = STATE_ANIMATION_DURATION + interpolator = Interpolators.CONTROL_STATE + start() + } + } else { + alpha = newAlpha + setColor(newClipColor) + baseLayer.setColor(newBaseColor) + layout.alpha = dimAlpha + } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 0f105376847f..61a1a986c091 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -36,4 +36,5 @@ interface ControlsUiController { controlId: String, @ControlAction.ResponseResult response: Int ) + fun onFocusChanged(controlWithState: ControlWithState?) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index fab6fc7357dd..2adfb1bd6d4f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -209,6 +209,20 @@ class ControlsUiControllerImpl @Inject constructor ( } } + override fun onFocusChanged(focusedControl: ControlWithState?) { + controlViewsById.forEach { key: ControlKey, viewHolder: ControlViewHolder -> + val state = controlsById.get(key) ?: return@forEach + val shouldBeDimmed = focusedControl != null && state != focusedControl + if (viewHolder.dimmed == shouldBeDimmed) { + return@forEach + } + + uiExecutor.execute { + viewHolder.dimmed = shouldBeDimmed + } + } + } + private fun startFavoritingActivity(context: Context, si: StructureInfo) { startTargetedActivity(context, si, ControlsFavoritingActivity::class.java) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt index a3368ef77a56..368d1399971d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt @@ -18,12 +18,10 @@ package com.android.systemui.controls.ui import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable -import android.view.View import android.service.controls.Control import android.service.controls.templates.ToggleTemplate - +import android.view.View import com.android.systemui.R -import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL class ToggleBehavior : Behavior { @@ -34,7 +32,7 @@ class ToggleBehavior : Behavior { override fun initialize(cvh: ControlViewHolder) { this.cvh = cvh - cvh.applyRenderInfo(false) + cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */) cvh.layout.setOnClickListener(View.OnClickListener() { ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked()) @@ -49,9 +47,9 @@ class ToggleBehavior : Behavior { val ld = cvh.layout.getBackground() as LayerDrawable clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + clipLayer.level = MAX_LEVEL val checked = template.isChecked() - clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL) cvh.applyRenderInfo(checked) } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt index e8785e12a883..dafd8b25ec2e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt @@ -16,11 +16,20 @@ package com.android.systemui.controls.ui -import android.os.Bundle +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator import android.content.Context import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable +import android.os.Bundle +import android.service.controls.Control +import android.service.controls.actions.FloatAction +import android.service.controls.templates.RangeTemplate +import android.service.controls.templates.ToggleRangeTemplate import android.util.Log +import android.util.MathUtils +import android.util.TypedValue import android.view.GestureDetector import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent @@ -29,19 +38,14 @@ import android.view.ViewGroup import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.widget.TextView -import android.service.controls.Control -import android.service.controls.actions.FloatAction -import android.service.controls.templates.RangeTemplate -import android.service.controls.templates.ToggleRangeTemplate -import android.util.TypedValue - +import com.android.systemui.Interpolators import com.android.systemui.R -import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL - +import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL import java.util.IllegalFormatException class ToggleRangeBehavior : Behavior { + private var rangeAnimator: ValueAnimator? = null lateinit var clipLayer: Drawable lateinit var template: ToggleRangeTemplate lateinit var control: Control @@ -61,20 +65,21 @@ class ToggleRangeBehavior : Behavior { status = cvh.status context = status.getContext() - cvh.applyRenderInfo(false) + cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */) val gestureListener = ToggleRangeGestureListener(cvh.layout) val gestureDetector = GestureDetector(context, gestureListener) cvh.layout.setOnTouchListener { v: View, e: MotionEvent -> if (gestureDetector.onTouchEvent(e)) { - return@setOnTouchListener true + // Don't return true to let the state list change to "pressed" + return@setOnTouchListener false } if (e.getAction() == MotionEvent.ACTION_UP && gestureListener.isDragging) { v.getParent().requestDisallowInterceptTouchEvent(false) gestureListener.isDragging = false endUpdateRange() - return@setOnTouchListener true + return@setOnTouchListener false } return@setOnTouchListener false @@ -87,17 +92,18 @@ class ToggleRangeBehavior : Behavior { currentStatusText = control.getStatusText() status.setText(currentStatusText) + // ControlViewHolder sets a long click listener, but we want to handle touch in + // here instead, otherwise we'll have state conflicts. + cvh.layout.setOnLongClickListener(null) + val ld = cvh.layout.getBackground() as LayerDrawable clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) - clipLayer.setLevel(MIN_LEVEL) template = control.getControlTemplate() as ToggleRangeTemplate rangeTemplate = template.getRange() val checked = template.isChecked() - val currentRatio = rangeTemplate.getCurrentValue() / - (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()) - updateRange(currentRatio, checked, /* isDragging */ false) + updateRange(rangeToLevelValue(rangeTemplate.currentValue), checked, /* isDragging */ false) cvh.applyRenderInfo(checked) @@ -146,9 +152,8 @@ class ToggleRangeBehavior : Behavior { } else { val value = arguments.getFloat( AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE) - val ratioDiff = (value - rangeTemplate.getCurrentValue()) / - (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()) - updateRange(ratioDiff, template.isChecked(), /* isDragging */ false) + val level = rangeToLevelValue(value - rangeTemplate.getCurrentValue()) + updateRange(level, template.isChecked(), /* isDragging */ false) endUpdateRange() true } @@ -170,15 +175,33 @@ class ToggleRangeBehavior : Behavior { fun beginUpdateRange() { status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) + ControlActionCoordinator.setFocusedElement(cvh, cvh.controlsController) } - fun updateRange(ratioDiff: Float, checked: Boolean, isDragging: Boolean) { - val changeAmount = if (checked) (MAX_LEVEL * ratioDiff).toInt() else MIN_LEVEL - val newLevel = Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, clipLayer.getLevel() + changeAmount)) - clipLayer.setLevel(newLevel) + fun updateRange(level: Int, checked: Boolean, isDragging: Boolean) { + val newLevel = if (checked) Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, level)) else MIN_LEVEL + + rangeAnimator?.cancel() + if (isDragging) { + clipLayer.level = newLevel + } else { + rangeAnimator = ValueAnimator.ofInt(cvh.clipLayer.level, newLevel).apply { + addUpdateListener { + cvh.clipLayer.level = it.animatedValue as Int + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + rangeAnimator = null + } + }) + duration = ControlViewHolder.STATE_ANIMATION_DURATION + interpolator = Interpolators.CONTROL_STATE + start() + } + } if (checked) { - val newValue = levelToRangeValue(clipLayer.getLevel()) + val newValue = levelToRangeValue(newLevel) currentRangeValue = format(rangeTemplate.getFormatString().toString(), DEFAULT_FORMAT, newValue) val text = if (isDragging) { @@ -206,9 +229,13 @@ class ToggleRangeBehavior : Behavior { } private fun levelToRangeValue(i: Int): Float { - val ratio = i.toFloat() / MAX_LEVEL - return rangeTemplate.getMinValue() + - (ratio * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())) + return MathUtils.constrainedMap(rangeTemplate.minValue, rangeTemplate.maxValue, + MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(), i.toFloat()) + } + + private fun rangeToLevelValue(i: Float): Int { + return MathUtils.constrainedMap(MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(), + rangeTemplate.minValue, rangeTemplate.maxValue, i).toInt() } fun endUpdateRange() { @@ -217,6 +244,7 @@ class ToggleRangeBehavior : Behavior { status.setText("$currentStatusText $currentRangeValue") cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(levelToRangeValue(clipLayer.getLevel())))) + ControlActionCoordinator.setFocusedElement(null, cvh.controlsController) } fun findNearestStep(value: Float): Float { @@ -247,6 +275,9 @@ class ToggleRangeBehavior : Behavior { } override fun onLongPress(e: MotionEvent) { + if (isDragging) { + return + } ControlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh) } @@ -265,8 +296,10 @@ class ToggleRangeBehavior : Behavior { isDragging = true } - this@ToggleRangeBehavior.updateRange(-xDiff / v.getWidth(), - /* checked */ true, /* isDragging */ true) + val ratioDiff = -xDiff / v.width + val changeAmount = ((MAX_LEVEL - MIN_LEVEL) * ratioDiff).toInt() + this@ToggleRangeBehavior.updateRange(clipLayer.level + changeAmount, + checked = true, isDragging = true) return true } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt index b02c9c8972fc..fd96cea1541e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt @@ -37,7 +37,7 @@ class TouchBehavior : Behavior { override fun initialize(cvh: ControlViewHolder) { this.cvh = cvh - cvh.applyRenderInfo(false) + cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */) cvh.layout.setOnClickListener(View.OnClickListener() { ControlActionCoordinator.touch(cvh, template.getTemplateId(), control) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 8c572fe8f842..88f96a8b19fe 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -24,6 +24,7 @@ import android.content.Context; import androidx.annotation.Nullable; import com.android.keyguard.KeyguardViewController; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.plugins.qs.QSFactory; @@ -33,6 +34,7 @@ import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; +import com.android.systemui.settings.CurrentUserContextTracker; import com.android.systemui.stackdivider.DividerModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -136,4 +138,15 @@ public abstract class SystemUIDefaultModule { @Binds abstract KeyguardViewController bindKeyguardViewController( StatusBarKeyguardViewManager statusBarKeyguardViewManager); + + @Singleton + @Provides + static CurrentUserContextTracker provideCurrentUserContextTracker( + Context context, + BroadcastDispatcher broadcastDispatcher) { + CurrentUserContextTracker tracker = + new CurrentUserContextTracker(context, broadcastDispatcher); + tracker.initialize(); + return tracker; + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index e6af36b2c86b..9bb253b3d890 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -43,7 +43,6 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Color; -import android.graphics.Insets; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -396,6 +395,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (preferredComponent == null) { Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed"); mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply(); + return; } mControlsController.seedFavoritesForComponent( @@ -2178,8 +2178,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); root.setOnApplyWindowInsetsListener((v, windowInsets) -> { if (mControlsUiController != null) { - Insets insets = windowInsets.getInsets(WindowInsets.Type.all()); - root.setPadding(insets.left, insets.top, insets.right, insets.bottom); + root.setPadding(windowInsets.getStableInsetLeft(), + windowInsets.getStableInsetTop(), + windowInsets.getStableInsetRight(), + windowInsets.getStableInsetBottom()); } return WindowInsets.CONSUMED; }); @@ -2315,4 +2317,4 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, && mControlsUiController.getAvailable() && !mControlsServiceInfos.isEmpty(); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java index 88ab9ef4b014..61524900b89b 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java @@ -49,20 +49,6 @@ public interface GLWallpaperRenderer { void onDrawFrame(); /** - * Notify ambient mode is changed. - * @param inAmbientMode true if in ambient mode. - * @param duration duration of transition. - */ - void updateAmbientMode(boolean inAmbientMode, long duration); - - /** - * Notify the wallpaper offsets changed. - * @param xOffset offset along x axis. - * @param yOffset offset along y axis. - */ - void updateOffsets(float xOffset, float yOffset); - - /** * Ask renderer to report the surface size it needs. */ Size reportSurfaceSize(); @@ -81,24 +67,4 @@ public interface GLWallpaperRenderer { */ void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args); - /** - * A proxy which owns surface holder. - */ - interface SurfaceProxy { - - /** - * Ask proxy to start rendering frame to surface. - */ - void requestRender(); - - /** - * Ask proxy to prepare render context. - */ - void preRender(); - - /** - * Ask proxy to destroy render context. - */ - void postRender(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java index 626d0cfed997..fa45ea1acb95 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java @@ -33,7 +33,6 @@ import static android.opengl.GLES20.glUniform1i; import static android.opengl.GLES20.glVertexAttribPointer; import android.graphics.Bitmap; -import android.graphics.Rect; import android.opengl.GLUtils; import android.util.Log; @@ -50,14 +49,9 @@ import java.nio.FloatBuffer; class ImageGLWallpaper { private static final String TAG = ImageGLWallpaper.class.getSimpleName(); - static final String A_POSITION = "aPosition"; - static final String A_TEXTURE_COORDINATES = "aTextureCoordinates"; - static final String U_PER85 = "uPer85"; - static final String U_REVEAL = "uReveal"; - static final String U_AOD2OPACITY = "uAod2Opacity"; - static final String U_TEXTURE = "uTexture"; - - private static final int HANDLE_UNDEFINED = -1; + private static final String A_POSITION = "aPosition"; + private static final String A_TEXTURE_COORDINATES = "aTextureCoordinates"; + private static final String U_TEXTURE = "uTexture"; private static final int POSITION_COMPONENT_COUNT = 2; private static final int TEXTURE_COMPONENT_COUNT = 2; private static final int BYTES_PER_FLOAT = 4; @@ -88,14 +82,9 @@ class ImageGLWallpaper { private int mAttrPosition; private int mAttrTextureCoordinates; - private int mUniAod2Opacity; - private int mUniPer85; - private int mUniReveal; private int mUniTexture; private int mTextureId; - private float[] mCurrentTexCoordinate; - ImageGLWallpaper(ImageGLProgram program) { mProgram = program; @@ -135,31 +124,9 @@ class ImageGLWallpaper { } private void setupUniforms() { - mUniAod2Opacity = mProgram.getUniformHandle(U_AOD2OPACITY); - mUniPer85 = mProgram.getUniformHandle(U_PER85); - mUniReveal = mProgram.getUniformHandle(U_REVEAL); mUniTexture = mProgram.getUniformHandle(U_TEXTURE); } - int getHandle(String name) { - switch (name) { - case A_POSITION: - return mAttrPosition; - case A_TEXTURE_COORDINATES: - return mAttrTextureCoordinates; - case U_AOD2OPACITY: - return mUniAod2Opacity; - case U_PER85: - return mUniPer85; - case U_REVEAL: - return mUniReveal; - case U_TEXTURE: - return mUniTexture; - default: - return HANDLE_UNDEFINED; - } - } - void draw() { glDrawArrays(GL_TRIANGLES, 0, VERTICES.length / 2); } @@ -201,87 +168,6 @@ class ImageGLWallpaper { } /** - * This method adjust s(x-axis), t(y-axis) texture coordinates to get current display area - * of texture and will be used during transition. - * The adjustment happens if either the width or height of the surface is larger than - * corresponding size of the display area. - * If both width and height are larger than corresponding size of the display area, - * the adjustment will happen at both s, t side. - * - * @param surface The size of the surface. - * @param scissor The display area. - * @param xOffset The offset amount along s axis. - * @param yOffset The offset amount along t axis. - */ - void adjustTextureCoordinates(Rect surface, Rect scissor, float xOffset, float yOffset) { - mCurrentTexCoordinate = TEXTURES.clone(); - - if (surface == null || scissor == null) { - mTextureBuffer.put(mCurrentTexCoordinate); - mTextureBuffer.position(0); - return; - } - - int surfaceWidth = surface.width(); - int surfaceHeight = surface.height(); - int scissorWidth = scissor.width(); - int scissorHeight = scissor.height(); - - if (surfaceWidth > scissorWidth) { - // Calculate the new s pos in pixels. - float pixelS = (float) Math.round((surfaceWidth - scissorWidth) * xOffset); - // Calculate the s pos in texture coordinate. - float coordinateS = pixelS / surfaceWidth; - // Calculate the percentage occupied by the scissor width in surface width. - float surfacePercentageW = (float) scissorWidth / surfaceWidth; - // Need also consider the case if surface height is smaller than scissor height. - if (surfaceHeight < scissorHeight) { - // We will narrow the surface percentage to keep aspect ratio. - surfacePercentageW *= (float) surfaceHeight / scissorHeight; - } - // Determine the final s pos, also limit the legal s pos to prevent from out of range. - float s = coordinateS + surfacePercentageW > 1f ? 1f - surfacePercentageW : coordinateS; - // Traverse the s pos in texture coordinates array and adjust the s pos accordingly. - for (int i = 0; i < mCurrentTexCoordinate.length; i += 2) { - // indices 2, 4 and 6 are the end of s coordinates. - if (i == 2 || i == 4 || i == 6) { - mCurrentTexCoordinate[i] = Math.min(1f, s + surfacePercentageW); - } else { - mCurrentTexCoordinate[i] = s; - } - } - } - - if (surfaceHeight > scissorHeight) { - // Calculate the new t pos in pixels. - float pixelT = (float) Math.round((surfaceHeight - scissorHeight) * yOffset); - // Calculate the t pos in texture coordinate. - float coordinateT = pixelT / surfaceHeight; - // Calculate the percentage occupied by the scissor height in surface height. - float surfacePercentageH = (float) scissorHeight / surfaceHeight; - // Need also consider the case if surface width is smaller than scissor width. - if (surfaceWidth < scissorWidth) { - // We will narrow the surface percentage to keep aspect ratio. - surfacePercentageH *= (float) surfaceWidth / scissorWidth; - } - // Determine the final t pos, also limit the legal t pos to prevent from out of range. - float t = coordinateT + surfacePercentageH > 1f ? 1f - surfacePercentageH : coordinateT; - // Traverse the t pos in texture coordinates array and adjust the t pos accordingly. - for (int i = 1; i < mCurrentTexCoordinate.length; i += 2) { - // indices 1, 3 and 11 are the end of t coordinates. - if (i == 1 || i == 3 || i == 11) { - mCurrentTexCoordinate[i] = Math.min(1f, t + surfacePercentageH); - } else { - mCurrentTexCoordinate[i] = t; - } - } - } - - mTextureBuffer.put(mCurrentTexCoordinate); - mTextureBuffer.position(0); - } - - /** * Called to dump current state. * @param prefix prefix. * @param fd fd. @@ -289,17 +175,5 @@ class ImageGLWallpaper { * @param args args. */ public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { - StringBuilder sb = new StringBuilder(); - sb.append('{'); - if (mCurrentTexCoordinate != null) { - for (int i = 0; i < mCurrentTexCoordinate.length; i++) { - sb.append(mCurrentTexCoordinate[i]).append(','); - if (i == mCurrentTexCoordinate.length - 1) { - sb.deleteCharAt(sb.length() - 1); - } - } - } - sb.append('}'); - out.print(prefix); out.print("mTexCoordinates="); out.println(sb.toString()); } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java deleted file mode 100644 index 703d5910500a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.glwallpaper; - -import static com.android.systemui.glwallpaper.ImageWallpaperRenderer.WallpaperTexture; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.util.Log; - -/** - * A helper class that computes threshold from a bitmap. - * Threshold will be computed each time the user picks a new image wallpaper. - */ -class ImageProcessHelper { - private static final String TAG = ImageProcessHelper.class.getSimpleName(); - private static final float DEFAULT_THRESHOLD = 0.8f; - private static final float DEFAULT_OTSU_THRESHOLD = 0f; - private static final float MAX_THRESHOLD = 0.89f; - private static final int MSG_UPDATE_THRESHOLD = 1; - - /** - * This color matrix will be applied to each pixel to get luminance from rgb by below formula: - * Luminance = .2126f * r + .7152f * g + .0722f * b. - */ - private static final float[] LUMINOSITY_MATRIX = new float[] { - .2126f, .0000f, .0000f, .0000f, .0000f, - .0000f, .7152f, .0000f, .0000f, .0000f, - .0000f, .0000f, .0722f, .0000f, .0000f, - .0000f, .0000f, .0000f, 1.000f, .0000f - }; - - private final Handler mHandler = new Handler(new Callback() { - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_THRESHOLD: - mThreshold = (float) msg.obj; - return true; - default: - return false; - } - } - }); - - private float mThreshold = DEFAULT_THRESHOLD; - - void start(WallpaperTexture texture) { - new ThresholdComputeTask(mHandler).execute(texture); - } - - float getThreshold() { - return Math.min(mThreshold, MAX_THRESHOLD); - } - - private static class ThresholdComputeTask extends AsyncTask<WallpaperTexture, Void, Float> { - private Handler mUpdateHandler; - - ThresholdComputeTask(Handler handler) { - super(handler); - mUpdateHandler = handler; - } - - @Override - protected Float doInBackground(WallpaperTexture... textures) { - WallpaperTexture texture = textures[0]; - final float[] threshold = new float[] {DEFAULT_THRESHOLD}; - if (texture == null) { - Log.e(TAG, "ThresholdComputeTask: WallpaperTexture not initialized"); - return threshold[0]; - } - - texture.use(bitmap -> { - if (bitmap != null) { - threshold[0] = new Threshold().compute(bitmap); - } else { - Log.e(TAG, "ThresholdComputeTask: Can't get bitmap"); - } - }); - return threshold[0]; - } - - @Override - protected void onPostExecute(Float result) { - Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_THRESHOLD, result); - mUpdateHandler.sendMessage(msg); - } - } - - private static class Threshold { - public float compute(Bitmap bitmap) { - Bitmap grayscale = toGrayscale(bitmap); - int[] histogram = getHistogram(grayscale); - boolean isSolidColor = isSolidColor(grayscale, histogram); - - // We will see gray wallpaper during the transition if solid color wallpaper is set, - // please refer to b/130360362#comment16. - // As a result, we use Percentile85 rather than Otsus if a solid color wallpaper is set. - ThresholdAlgorithm algorithm = isSolidColor ? new Percentile85() : new Otsus(); - return algorithm.compute(grayscale, histogram); - } - - private Bitmap toGrayscale(Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - - Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig(), - false /* hasAlpha */, bitmap.getColorSpace()); - Canvas canvas = new Canvas(grayscale); - ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX); - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(cm)); - canvas.drawBitmap(bitmap, new Matrix(), paint); - - return grayscale; - } - - private int[] getHistogram(Bitmap grayscale) { - int width = grayscale.getWidth(); - int height = grayscale.getHeight(); - - // TODO: Fine tune the performance here, tracking on b/123615079. - int[] histogram = new int[256]; - for (int row = 0; row < height; row++) { - for (int col = 0; col < width; col++) { - int pixel = grayscale.getPixel(col, row); - int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel); - histogram[y]++; - } - } - - return histogram; - } - - private boolean isSolidColor(Bitmap bitmap, int[] histogram) { - boolean solidColor = false; - int pixels = bitmap.getWidth() * bitmap.getHeight(); - - // In solid color case, only one element of histogram has value, - // which is pixel counts and the value of other elements should be 0. - for (int value : histogram) { - if (value != 0 && value != pixels) { - break; - } - if (value == pixels) { - solidColor = true; - break; - } - } - return solidColor; - } - } - - private static class Percentile85 implements ThresholdAlgorithm { - @Override - public float compute(Bitmap bitmap, int[] histogram) { - float per85 = DEFAULT_THRESHOLD; - int pixelCount = bitmap.getWidth() * bitmap.getHeight(); - float[] acc = new float[256]; - for (int i = 0; i < acc.length; i++) { - acc[i] = (float) histogram[i] / pixelCount; - float prev = i == 0 ? 0f : acc[i - 1]; - float next = acc[i]; - float idx = (float) (i + 1) / 255; - float sum = prev + next; - if (prev < 0.85f && sum >= 0.85f) { - per85 = idx; - } - if (i > 0) { - acc[i] += acc[i - 1]; - } - } - return per85; - } - } - - private static class Otsus implements ThresholdAlgorithm { - @Override - public float compute(Bitmap bitmap, int[] histogram) { - float threshold = DEFAULT_OTSU_THRESHOLD; - float maxVariance = 0; - float pixelCount = bitmap.getWidth() * bitmap.getHeight(); - float[] w = new float[2]; - float[] m = new float[2]; - float[] u = new float[2]; - - for (int i = 0; i < histogram.length; i++) { - m[1] += i * histogram[i]; - } - - w[1] = pixelCount; - for (int tonalValue = 0; tonalValue < histogram.length; tonalValue++) { - float dU; - float variance; - float numPixels = histogram[tonalValue]; - float tmp = numPixels * tonalValue; - w[0] += numPixels; - w[1] -= numPixels; - - if (w[0] == 0 || w[1] == 0) { - continue; - } - - m[0] += tmp; - m[1] -= tmp; - u[0] = m[0] / w[0]; - u[1] = m[1] / w[1]; - dU = u[0] - u[1]; - variance = w[0] * w[1] * dU * dU; - - if (variance > maxVariance) { - threshold = (tonalValue + 1f) / histogram.length; - maxVariance = variance; - } - } - return threshold; - } - } - - private interface ThresholdAlgorithm { - float compute(Bitmap bitmap, int[] histogram); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java deleted file mode 100644 index f815b5d476ec..000000000000 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.glwallpaper; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.util.Log; - -import com.android.systemui.Interpolators; - -/** - * Use ValueAnimator and appropriate interpolator to control the progress of reveal transition. - * The transition will happen while getting awake and quit events. - */ -class ImageRevealHelper { - private static final String TAG = ImageRevealHelper.class.getSimpleName(); - private static final float MAX_REVEAL = 0f; - private static final float MIN_REVEAL = 1f; - private static final boolean DEBUG = true; - - private final ValueAnimator mAnimator; - private final RevealStateListener mRevealListener; - private float mReveal = MAX_REVEAL; - private boolean mAwake = false; - - ImageRevealHelper(RevealStateListener listener) { - mRevealListener = listener; - mAnimator = ValueAnimator.ofFloat(); - mAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mAnimator.addUpdateListener(animator -> { - mReveal = (float) animator.getAnimatedValue(); - if (mRevealListener != null) { - mRevealListener.onRevealStateChanged(); - } - }); - mAnimator.addListener(new AnimatorListenerAdapter() { - private boolean mIsCanceled; - - @Override - public void onAnimationCancel(Animator animation) { - mIsCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (!mIsCanceled && mRevealListener != null) { - if (DEBUG) { - Log.d(TAG, "transition end"); - } - mRevealListener.onRevealEnd(); - } - mIsCanceled = false; - } - - @Override - public void onAnimationStart(Animator animation) { - if (mRevealListener != null) { - if (DEBUG) { - Log.d(TAG, "transition start"); - } - mRevealListener.onRevealStart(true /* animate */); - } - } - }); - } - - public float getReveal() { - return mReveal; - } - - void updateAwake(boolean awake, long duration) { - if (DEBUG) { - Log.d(TAG, "updateAwake: awake=" + awake + ", duration=" + duration); - } - mAnimator.cancel(); - mAwake = awake; - if (duration == 0) { - // We are transiting from home to aod or aod to home directly, - // we don't need to do transition in these cases. - mReveal = mAwake ? MAX_REVEAL : MIN_REVEAL; - mRevealListener.onRevealStart(false /* animate */); - mRevealListener.onRevealStateChanged(); - mRevealListener.onRevealEnd(); - } else { - mAnimator.setDuration(duration); - mAnimator.setFloatValues(mReveal, mAwake ? MAX_REVEAL : MIN_REVEAL); - mAnimator.start(); - } - } - - /** - * A listener to trace value changes of reveal. - */ - public interface RevealStateListener { - - /** - * Called back while reveal status changes. - */ - void onRevealStateChanged(); - - /** - * Called back while reveal starts. - */ - void onRevealStart(boolean animate); - - /** - * Called back while reveal ends. - */ - void onRevealEnd(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index e9ddb3831b1a..1a0356c4446d 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -19,18 +19,14 @@ package com.android.systemui.glwallpaper; import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT; import static android.opengl.GLES20.glClear; import static android.opengl.GLES20.glClearColor; -import static android.opengl.GLES20.glUniform1f; import static android.opengl.GLES20.glViewport; import android.app.WallpaperManager; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Rect; import android.util.Log; -import android.util.MathUtils; import android.util.Size; -import android.view.DisplayInfo; import com.android.systemui.R; @@ -42,57 +38,24 @@ import java.util.function.Consumer; /** * A GL renderer for image wallpaper. */ -public class ImageWallpaperRenderer implements GLWallpaperRenderer, - ImageRevealHelper.RevealStateListener { +public class ImageWallpaperRenderer implements GLWallpaperRenderer { private static final String TAG = ImageWallpaperRenderer.class.getSimpleName(); - private static final float SCALE_VIEWPORT_MIN = 1f; - private static final float SCALE_VIEWPORT_MAX = 1.1f; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private final ImageGLProgram mProgram; private final ImageGLWallpaper mWallpaper; - private final ImageProcessHelper mImageProcessHelper; - private final ImageRevealHelper mImageRevealHelper; - - private SurfaceProxy mProxy; - private final Rect mScissor; private final Rect mSurfaceSize = new Rect(); - private final Rect mViewport = new Rect(); - private boolean mScissorMode; - private float mXOffset; - private float mYOffset; private final WallpaperTexture mTexture; - public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) { + public ImageWallpaperRenderer(Context context) { final WallpaperManager wpm = context.getSystemService(WallpaperManager.class); if (wpm == null) { Log.w(TAG, "WallpaperManager not available"); } mTexture = new WallpaperTexture(wpm); - DisplayInfo displayInfo = new DisplayInfo(); - context.getDisplay().getDisplayInfo(displayInfo); - - // We only do transition in portrait currently, b/137962047. - int orientation = context.getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - mScissor = new Rect(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); - } else { - mScissor = new Rect(0, 0, displayInfo.logicalHeight, displayInfo.logicalWidth); - } - - mProxy = proxy; mProgram = new ImageGLProgram(context); mWallpaper = new ImageGLWallpaper(mProgram); - mImageProcessHelper = new ImageProcessHelper(); - mImageRevealHelper = new ImageRevealHelper(this); - - startProcessingImage(); - } - - protected void startProcessingImage() { - // Compute threshold of the image, this is an async work. - mImageProcessHelper.start(mTexture); } @Override @@ -121,103 +84,26 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer, @Override public void onDrawFrame() { - float threshold = mImageProcessHelper.getThreshold(); - float reveal = mImageRevealHelper.getReveal(); - - glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1); - glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), threshold); - glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal); - glClear(GL_COLOR_BUFFER_BIT); - // We only need to scale viewport while doing transition. - if (mScissorMode) { - scaleViewport(reveal); - } else { - glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height()); - } + glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height()); mWallpaper.useTexture(); mWallpaper.draw(); } @Override - public void updateAmbientMode(boolean inAmbientMode, long duration) { - mImageRevealHelper.updateAwake(!inAmbientMode, duration); - } - - @Override - public void updateOffsets(float xOffset, float yOffset) { - mXOffset = xOffset; - mYOffset = yOffset; - int left = (int) ((mSurfaceSize.width() - mScissor.width()) * xOffset); - int right = left + mScissor.width(); - mScissor.set(left, mScissor.top, right, mScissor.bottom); - } - - @Override public Size reportSurfaceSize() { - mTexture.use(null); + mTexture.use(null /* consumer */); mSurfaceSize.set(mTexture.getTextureDimensions()); return new Size(mSurfaceSize.width(), mSurfaceSize.height()); } @Override public void finish() { - mProxy = null; - } - - private void scaleViewport(float reveal) { - int left = mScissor.left; - int top = mScissor.top; - int width = mScissor.width(); - int height = mScissor.height(); - // Interpolation between SCALE_VIEWPORT_MAX and SCALE_VIEWPORT_MIN by reveal. - float vpScaled = MathUtils.lerp(SCALE_VIEWPORT_MIN, SCALE_VIEWPORT_MAX, reveal); - // Calculate the offset amount from the lower left corner. - float offset = (SCALE_VIEWPORT_MIN - vpScaled) / 2; - // Change the viewport. - mViewport.set((int) (left + width * offset), (int) (top + height * offset), - (int) (width * vpScaled), (int) (height * vpScaled)); - glViewport(mViewport.left, mViewport.top, mViewport.right, mViewport.bottom); - } - - @Override - public void onRevealStateChanged() { - mProxy.requestRender(); - } - - @Override - public void onRevealStart(boolean animate) { - if (animate) { - mScissorMode = true; - // Use current display area of texture. - mWallpaper.adjustTextureCoordinates(mSurfaceSize, mScissor, mXOffset, mYOffset); - } - mProxy.preRender(); - } - - @Override - public void onRevealEnd() { - if (mScissorMode) { - mScissorMode = false; - // reset texture coordinates to use full texture. - mWallpaper.adjustTextureCoordinates(null, null, 0, 0); - // We need draw full texture back before finishing render. - mProxy.requestRender(); - } - mProxy.postRender(); } @Override public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { - out.print(prefix); out.print("mProxy="); out.print(mProxy); out.print(prefix); out.print("mSurfaceSize="); out.print(mSurfaceSize); - out.print(prefix); out.print("mScissor="); out.print(mScissor); - out.print(prefix); out.print("mViewport="); out.print(mViewport); - out.print(prefix); out.print("mScissorMode="); out.print(mScissorMode); - out.print(prefix); out.print("mXOffset="); out.print(mXOffset); - out.print(prefix); out.print("mYOffset="); out.print(mYOffset); - out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold()); - out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal()); out.print(prefix); out.print("mWcgContent="); out.print(isWcgContent()); mWallpaper.dump(prefix, fd, out, args); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 62efd8ce4cee..8492fef97df5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -82,6 +82,7 @@ public class MediaControlPanel { protected ComponentName mRecvComponent; private MediaDevice mDevice; private boolean mIsRegistered = false; + private String mKey; private final int[] mActionIds; @@ -203,14 +204,15 @@ public class MediaControlPanel { * @param bgColor * @param contentIntent * @param appNameString - * @param device + * @param key */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, - int bgColor, PendingIntent contentIntent, String appNameString) { + int bgColor, PendingIntent contentIntent, String appNameString, String key) { mToken = token; mForegroundColor = iconColor; mBackgroundColor = bgColor; mController = new MediaController(mContext, mToken); + mKey = key; MediaMetadata mediaMetadata = mController.getMetadata(); @@ -326,6 +328,14 @@ public class MediaControlPanel { } /** + * Return the original notification's key + * @return The notification key + */ + public String getKey() { + return mKey; + } + + /** * Check whether this player has an attached media session. * @return whether there is a controller with a current media session. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index b7658a9f178d..51c157a56560 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -61,6 +61,7 @@ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { if (!data.enabled) { seekBarView.setEnabled(false) seekBarView.getThumb().setAlpha(0) + seekBarView.setProgress(0) elapsedTimeView.setText("") totalTimeView.setText("") return diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index dd83e42cde2d..142510030a5f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -77,13 +77,25 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L val position = playbackState?.position?.toInt() val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() - val enabled = if (duration != null && duration <= 0) false else true + val enabled = if (playbackState == null || + playbackState?.getState() == PlaybackState.STATE_NONE || + (duration != null && duration <= 0)) false else true _data = Progress(enabled, seekAvailable, position, duration, color) if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } } + /** + * Puts the seek bar into a resumption state. + * + * This should be called when the media session behind the controller has been destroyed. + */ + @AnyThread + fun clearController() = bgExecutor.execute { + _data = _data.copy(enabled = false) + } + @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val currentPosition = controller?.playbackState?.position?.toInt() diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index c3779efcf4b2..ba9a30fb554f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -339,18 +339,18 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio @Override public void onPipTransitionFinished(ComponentName activity, int direction) { - onPipTransitionFinishedOrCanceled(); + onPipTransitionFinishedOrCanceled(direction); } @Override public void onPipTransitionCanceled(ComponentName activity, int direction) { - onPipTransitionFinishedOrCanceled(); + onPipTransitionFinishedOrCanceled(direction); } - private void onPipTransitionFinishedOrCanceled() { + private void onPipTransitionFinishedOrCanceled(int direction) { // Re-enable touches after the animation completes mTouchHandler.setTouchEnabled(true); - mTouchHandler.onPinnedStackAnimationEnded(); + mTouchHandler.onPinnedStackAnimationEnded(direction); mMenuController.onPinnedStackAnimationEnded(); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 2b9b1716cb18..ec15dd16f46e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -54,6 +54,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; @@ -129,9 +130,7 @@ public class PipMenuActivity extends Activity { } }; - private Handler mHandler = new Handler(); - private Messenger mToControllerMessenger; - private Messenger mMessenger = new Messenger(new Handler() { + private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -174,7 +173,9 @@ public class PipMenuActivity extends Activity { } } } - }); + }; + private Messenger mToControllerMessenger; + private Messenger mMessenger = new Messenger(mHandler); private final Runnable mFinishRunnable = new Runnable() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index d660b670446b..61ed40d5d782 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -30,6 +30,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Debug; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; @@ -122,7 +123,7 @@ public class PipMenuActivityController { private boolean mStartActivityRequested; private long mStartActivityRequestedTime; private Messenger mToActivityMessenger; - private Handler mHandler = new Handler() { + private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -133,15 +134,15 @@ public class PipMenuActivityController { break; } case MESSAGE_EXPAND_PIP: { - mListeners.forEach(l -> l.onPipExpand()); + mListeners.forEach(Listener::onPipExpand); break; } case MESSAGE_DISMISS_PIP: { - mListeners.forEach(l -> l.onPipDismiss()); + mListeners.forEach(Listener::onPipDismiss); break; } case MESSAGE_SHOW_MENU: { - mListeners.forEach(l -> l.onPipShowMenu()); + mListeners.forEach(Listener::onPipShowMenu); break; } case MESSAGE_UPDATE_ACTIVITY_CALLBACK: { @@ -259,6 +260,8 @@ public class PipMenuActivityController { if (DEBUG) { Log.d(TAG, "showMenu() state=" + menuState + " hasActivity=" + (mToActivityMessenger != null) + + " allowMenuTimeout=" + allowMenuTimeout + + " willResizeMenu=" + willResizeMenu + " callers=\n" + Debug.getCallers(5, " ")); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index a8a5d896537f..00f693de8f4d 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -168,6 +168,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, void synchronizePinnedStackBounds() { cancelAnimations(); mBounds.set(mPipTaskOrganizer.getLastReportedBounds()); + mFloatingContentCoordinator.onContentMoved(this); } /** diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java index 0b076559ae36..d80f18a983ee 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -56,7 +56,6 @@ public class PipResizeGestureHandler { private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private final PipBoundsHandler mPipBoundsHandler; - private final PipTouchHandler mPipTouchHandler; private final PipMotionHelper mMotionHelper; private final int mDisplayId; private final Executor mMainExecutor; @@ -70,10 +69,10 @@ public class PipResizeGestureHandler { private final Rect mTmpBounds = new Rect(); private final int mDelta; - private boolean mAllowGesture = false; + private boolean mAllowGesture; private boolean mIsAttached; private boolean mIsEnabled; - private boolean mEnablePipResize; + private boolean mEnableUserResize; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; @@ -82,21 +81,20 @@ public class PipResizeGestureHandler { private int mCtrlType; public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler, - PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper, - DeviceConfigProxy deviceConfig, PipTaskOrganizer pipTaskOrganizer) { + PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig, + PipTaskOrganizer pipTaskOrganizer) { final Resources res = context.getResources(); context.getDisplay().getMetrics(mDisplayMetrics); mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); mPipBoundsHandler = pipBoundsHandler; - mPipTouchHandler = pipTouchHandler; mMotionHelper = motionHelper; mPipTaskOrganizer = pipTaskOrganizer; context.getDisplay().getRealSize(mMaxSize); mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); - mEnablePipResize = DeviceConfig.getBoolean( + mEnableUserResize = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_USER_RESIZE, /* defaultValue = */ true); @@ -105,7 +103,7 @@ public class PipResizeGestureHandler { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { if (properties.getKeyset().contains(PIP_USER_RESIZE)) { - mEnablePipResize = properties.getBoolean( + mEnableUserResize = properties.getBoolean( PIP_USER_RESIZE, /* defaultValue = */ true); } } @@ -134,7 +132,7 @@ public class PipResizeGestureHandler { } private void updateIsEnabled() { - boolean isEnabled = mIsAttached && mEnablePipResize; + boolean isEnabled = mIsAttached && mEnableUserResize; if (isEnabled == mIsEnabled) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 350ce293a13f..f5c83c1fffd7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -16,6 +16,7 @@ package com.android.systemui.pip.phone; +import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; @@ -56,6 +57,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.systemui.R; +import com.android.systemui.pip.PipAnimationController; import com.android.systemui.pip.PipBoundsHandler; import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.pip.PipTaskOrganizer; @@ -229,7 +231,7 @@ public class PipTouchHandler { mMotionHelper = new PipMotionHelper(mContext, activityTaskManager, pipTaskOrganizer, mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator); mPipResizeGestureHandler = - new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper, + new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper, deviceConfig, pipTaskOrganizer); mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler, () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), @@ -266,6 +268,10 @@ public class PipTouchHandler { mMagnetizedPip = mMotionHelper.getMagnetizedPip(); mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + + // Set the magnetic field radius equal to twice the size of the target. + mMagneticTarget.setMagneticFieldRadiusPx(targetSize * 2); + mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener); mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { @Override @@ -339,11 +345,16 @@ public class PipTouchHandler { mPipResizeGestureHandler.onActivityUnpinned(); } - public void onPinnedStackAnimationEnded() { + public void onPinnedStackAnimationEnded( + @PipAnimationController.TransitionDirection int direction) { // Always synchronize the motion helper bounds once PiP animations finish mMotionHelper.synchronizePinnedStackBounds(); updateMovementBounds(); - mResizedBounds.set(mMotionHelper.getBounds()); + if (direction == TRANSITION_DIRECTION_TO_PIP) { + // updates mResizedBounds only if it's an entering PiP animation + // mResized should be otherwise updated in setMenuState. + mResizedBounds.set(mMotionHelper.getBounds()); + } if (mShowPipMenuOnAnimationEnd) { mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), @@ -504,9 +515,6 @@ public class PipTouchHandler { mTargetView.setTranslationY(mTargetViewContainer.getHeight()); mTargetViewContainer.setVisibility(View.VISIBLE); - // Set the magnetic field radius to half of PIP's width. - mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width()); - // Cancel in case we were in the middle of animating it out. mMagneticTargetAnimator.cancel(); mMagneticTargetAnimator diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index e636707a9722..e4bcb0921284 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -99,15 +99,14 @@ public class QSMediaPlayer extends MediaControlPanel { * @param bgColor background color * @param actionsContainer a LinearLayout containing the media action buttons * @param notif reference to original notification - * @param device current playback device + * @param key original notification's key */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, - int bgColor, View actionsContainer, Notification notif) { + int bgColor, View actionsContainer, Notification notif, String key) { String appName = Notification.Builder.recoverBuilder(getContext(), notif) .loadHeaderAppName(); - super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, - appName); + super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, appName, key); // Media controls LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; @@ -171,6 +170,8 @@ public class QSMediaPlayer extends MediaControlPanel { public void clearControls() { super.clearControls(); + mSeekBarViewModel.clearController(); + View guts = mMediaNotifView.findViewById(R.id.media_guts); View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 0566b2e621db..40c8aadfd5d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -208,9 +208,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param bgColor * @param actionsContainer * @param notif + * @param key */ public void addMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor, - View actionsContainer, StatusBarNotification notif) { + View actionsContainer, StatusBarNotification notif, String key) { if (!useQsMediaPlayer(mContext)) { // Shouldn't happen, but just in case Log.e(TAG, "Tried to add media session without player!"); @@ -225,13 +226,12 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne String packageName = notif.getPackageName(); for (QSMediaPlayer p : mMediaPlayers) { if (p.getMediaSessionToken().equals(token)) { - Log.d(TAG, "a player for this session already exists"); + Log.d(TAG, "Found matching player by token " + packageName); player = p; break; - } - - if (packageName.equals(p.getMediaPlayerPackage())) { - Log.d(TAG, "found an old session for this app"); + } else if (packageName.equals(p.getMediaPlayerPackage()) && key.equals(p.getKey())) { + // Also match if it's the same package and notification key + Log.d(TAG, "Found matching player by package " + packageName + ", " + key); player = p; break; } @@ -267,7 +267,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne Log.d(TAG, "setting player session"); player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer, - notif.getNotification()); + notif.getNotification(), key); if (mMediaPlayers.size() > 0) { ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index 0ba4cb159024..794677912626 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -67,9 +67,10 @@ public class QuickQSMediaPlayer extends MediaControlPanel { * @param actionsToShow indices of which actions to display in the mini player * (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT) * @param contentIntent Intent to send when user taps on the view + * @param key original notification's key */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor, - View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) { + View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) { // Only update if this is a different session and currently playing String oldPackage = ""; if (getController() != null) { @@ -84,7 +85,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel { return; } - super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null); + super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, key); LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; int i = 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 666323766c12..9cee7e7ccba4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -72,6 +72,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> state.value = isRecording || isStarting; state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.label = mContext.getString(R.string.quick_settings_screen_record_label); if (isRecording) { state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord); diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt new file mode 100644 index 000000000000..fa1b0267fafa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings + +import android.content.Context +import android.os.UserHandle +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.util.Assert +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Tracks a reference to the context for the current user + */ +@Singleton +class CurrentUserContextTracker @Inject constructor( + private val sysuiContext: Context, + broadcastDispatcher: BroadcastDispatcher +) { + private val userTracker: CurrentUserTracker + var currentUserContext: Context + + init { + userTracker = object : CurrentUserTracker(broadcastDispatcher) { + override fun onUserSwitched(newUserId: Int) { + handleUserSwitched(newUserId) + } + } + + currentUserContext = makeUserContext(userTracker.currentUserId) + } + + fun initialize() { + userTracker.startTracking() + } + + private fun handleUserSwitched(newUserId: Int) { + currentUserContext = makeUserContext(newUserId) + } + + private fun makeUserContext(uid: Int): Context { + Assert.isMainThread() + return sysuiContext.createContextAsUser( + UserHandle.getUserHandleForUid(userTracker.currentUserId), 0) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 0d7715958995..25f1a974bc36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -250,7 +250,8 @@ class NotificationShadeDepthController @Inject constructor( private fun updateShadeBlur() { var newBlur = 0 val state = statusBarStateController.state - if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) { + if ((state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) && + !keyguardStateController.isKeyguardFadingAway) { newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) } shadeSpring.animateTo(newBlur) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index d37e16b17620..75493f89993b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -699,7 +699,8 @@ public class NotificationEntryManager implements entry, oldImportances.get(entry.getKey()), oldAdjustments.get(entry.getKey()), - NotificationUiAdjustment.extractFromNotificationEntry(entry)); + NotificationUiAdjustment.extractFromNotificationEntry(entry), + mInflationCallback); } updateNotifications("updateNotificationRanking"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 2a3b2b7d815d..3fde2ed249d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager.alertAgain; +import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; import android.annotation.Nullable; @@ -29,7 +29,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder; +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java index f4c4924b4b9a..710b137d2795 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java @@ -51,5 +51,6 @@ public interface NotificationRowBinder { NotificationEntry entry, @Nullable Integer oldImportance, NotificationUiAdjustment oldAdjustment, - NotificationUiAdjustment newAdjustment); + NotificationUiAdjustment newAdjustment, + NotificationRowContentBinder.InflationCallback callback); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 73f12f86e52e..e6a4cff0d893 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -180,13 +180,14 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationEntry entry, @Nullable Integer oldImportance, NotificationUiAdjustment oldAdjustment, - NotificationUiAdjustment newAdjustment) { + NotificationUiAdjustment newAdjustment, + NotificationRowContentBinder.InflationCallback callback) { if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) { if (entry.rowExists()) { ExpandableNotificationRow row = entry.getRow(); row.reset(); updateRow(entry, row); - inflateContentViews(entry, row, null /* callback */); + inflateContentViews(entry, row, callback); } else { // Once the RowInflaterTask is done, it will pick up the updated entry, so // no-op here. @@ -221,7 +222,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private void inflateContentViews( NotificationEntry entry, ExpandableNotificationRow row, - NotificationRowContentBinder.InflationCallback inflationCallback) { + @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) { final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance()); final boolean isLowPriority = entry.isAmbient(); @@ -238,7 +239,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mRowContentBindStage.requestRebind(entry, en -> { row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); row.setIsLowPriority(isLowPriority); - inflationCallback.onAsyncInflationFinished(en); + if (inflationCallback != null) { + inflationCallback.onAsyncInflationFinished(en); + } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 565a082533a7..78ee5f25b111 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -30,6 +30,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.CurrentUserContextTracker; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -45,7 +46,6 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -53,13 +53,14 @@ import com.android.systemui.statusbar.notification.logging.NotificationPanelLogg import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.leak.LeakDetector; import java.util.concurrent.Executor; +import javax.inject.Provider; import javax.inject.Singleton; import dagger.Binds; @@ -109,7 +110,9 @@ public interface NotificationsModule { HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, LauncherApps launcherApps, - ShortcutManager shortcutManager) { + ShortcutManager shortcutManager, + CurrentUserContextTracker contextTracker, + Provider<PriorityOnboardingDialogController.Builder> builderProvider) { return new NotificationGutsManager( context, visualStabilityManager, @@ -119,7 +122,9 @@ public interface NotificationsModule { highPriorityProvider, notificationManager, launcherApps, - shortcutManager); + shortcutManager, + contextTracker, + builderProvider); } /** Provides an instance of {@link VisualStabilityManager} */ @@ -130,27 +135,6 @@ public interface NotificationsModule { return new VisualStabilityManager(notificationEntryManager, handler); } - /** Provides an instance of {@link NotificationAlertingManager} */ - @Singleton - @Provides - static NotificationAlertingManager provideNotificationAlertingManager( - NotificationEntryManager notificationEntryManager, - NotificationRemoteInputManager remoteInputManager, - VisualStabilityManager visualStabilityManager, - StatusBarStateController statusBarStateController, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationListener notificationListener, - HeadsUpManager headsUpManager) { - return new NotificationAlertingManager( - notificationEntryManager, - remoteInputManager, - visualStabilityManager, - statusBarStateController, - notificationInterruptStateProvider, - notificationListener, - headsUpManager); - } - /** Provides an instance of {@link NotificationLogger} */ @Singleton @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java deleted file mode 100644 index a7b1f37edf0e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.headsup; - -import androidx.annotation.NonNull; - -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Controller class for old pipeline heads up view binding. It listens to - * {@link NotificationEntryManager} entry events and appropriately binds or unbinds the heads up - * view. - * - * This has a subtle contract with {@link NotificationAlertingManager} where this controller handles - * the heads up binding, but {@link NotificationAlertingManager} listens for general inflation - * events to actually mark it heads up/update. In the new pipeline, we combine the classes. - * See {@link HeadsUpCoordinator}. - */ -@Singleton -public class HeadsUpBindController { - private final HeadsUpViewBinder mHeadsUpViewBinder; - private final NotificationInterruptStateProvider mInterruptStateProvider; - - @Inject - HeadsUpBindController( - HeadsUpViewBinder headsUpViewBinder, - NotificationInterruptStateProvider notificationInterruptStateProvider) { - mInterruptStateProvider = notificationInterruptStateProvider; - mHeadsUpViewBinder = headsUpViewBinder; - } - - /** - * Attach this controller and add its listeners. - */ - public void attach( - NotificationEntryManager entryManager, - HeadsUpManager headsUpManager) { - entryManager.addCollectionListener(mCollectionListener); - headsUpManager.addListener(mOnHeadsUpChangedListener); - } - - private NotifCollectionListener mCollectionListener = new NotifCollectionListener() { - @Override - public void onEntryAdded(NotificationEntry entry) { - if (mInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpViewBinder.bindHeadsUpView(entry, null); - } - } - - @Override - public void onEntryUpdated(NotificationEntry entry) { - if (mInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpViewBinder.bindHeadsUpView(entry, null); - } - } - - @Override - public void onEntryCleanUp(NotificationEntry entry) { - mHeadsUpViewBinder.abortBindCallback(entry); - } - }; - - private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() { - @Override - public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) { - if (!isHeadsUp) { - mHeadsUpViewBinder.unbindHeadsUpView(entry); - } - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 5fac5b1cf159..7f2f898565d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -28,7 +28,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer -import com.android.systemui.statusbar.notification.headsup.HeadsUpBindController +import com.android.systemui.statusbar.notification.interruption.HeadsUpController import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper @@ -36,7 +36,7 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.policy.RemoteInputUriController import dagger.Lazy import java.io.FileDescriptor @@ -66,7 +66,7 @@ class NotificationsControllerImpl @Inject constructor( private val groupManager: NotificationGroupManager, private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, private val headsUpManager: HeadsUpManager, - private val headsUpBindController: HeadsUpBindController, + private val headsUpController: HeadsUpController, private val headsUpViewBinder: HeadsUpViewBinder ) : NotificationsController { @@ -112,7 +112,7 @@ class NotificationsControllerImpl @Inject constructor( groupAlertTransferHelper.bind(entryManager, groupManager) headsUpManager.addListener(groupManager) headsUpManager.addListener(groupAlertTransferHelper) - headsUpBindController.attach(entryManager, headsUpManager) + headsUpController.attach(entryManager, headsUpManager) groupManager.setHeadsUpManager(headsUpManager) groupAlertTransferHelper.setHeadsUpManager(headsUpManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java index 5d070981f81b..6d14ccf85716 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,117 +22,117 @@ import android.app.Notification; import android.service.notification.StatusBarNotification; import android.util.Log; -import com.android.internal.statusbar.NotificationVisibility; +import androidx.annotation.NonNull; + import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.dagger.NotificationsModule; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -/** Handles heads-up and pulsing behavior driven by notification changes. */ -public class NotificationAlertingManager { - - private static final String TAG = "NotifAlertManager"; +import javax.inject.Inject; +import javax.inject.Singleton; +/** + * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager} + * entry events and appropriately binds or unbinds the heads up view and promotes it to the top + * of the screen. + */ +@Singleton +public class HeadsUpController { + private final HeadsUpViewBinder mHeadsUpViewBinder; + private final NotificationInterruptStateProvider mInterruptStateProvider; private final NotificationRemoteInputManager mRemoteInputManager; private final VisualStabilityManager mVisualStabilityManager; private final StatusBarStateController mStatusBarStateController; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final NotificationListener mNotificationListener; + private final HeadsUpManager mHeadsUpManager; - private HeadsUpManager mHeadsUpManager; - - /** - * Injected constructor. See {@link NotificationsModule}. - */ - public NotificationAlertingManager( - NotificationEntryManager notificationEntryManager, + @Inject + HeadsUpController( + HeadsUpViewBinder headsUpViewBinder, + NotificationInterruptStateProvider notificationInterruptStateProvider, + HeadsUpManager headsUpManager, NotificationRemoteInputManager remoteInputManager, - VisualStabilityManager visualStabilityManager, StatusBarStateController statusBarStateController, - NotificationInterruptStateProvider notificationInterruptionStateProvider, - NotificationListener notificationListener, - HeadsUpManager headsUpManager) { + VisualStabilityManager visualStabilityManager, + NotificationListener notificationListener) { + mHeadsUpViewBinder = headsUpViewBinder; + mHeadsUpManager = headsUpManager; + mInterruptStateProvider = notificationInterruptStateProvider; mRemoteInputManager = remoteInputManager; - mVisualStabilityManager = visualStabilityManager; mStatusBarStateController = statusBarStateController; - mNotificationInterruptStateProvider = notificationInterruptionStateProvider; + mVisualStabilityManager = visualStabilityManager; mNotificationListener = notificationListener; - mHeadsUpManager = headsUpManager; + } - notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { - @Override - public void onEntryInflated(NotificationEntry entry) { - showAlertingView(entry); - } + /** + * Attach this controller and add its listeners. + */ + public void attach( + NotificationEntryManager entryManager, + HeadsUpManager headsUpManager) { + entryManager.addCollectionListener(mCollectionListener); + headsUpManager.addListener(mOnHeadsUpChangedListener); + } - @Override - public void onPreEntryUpdated(NotificationEntry entry) { - updateAlertState(entry); + private NotifCollectionListener mCollectionListener = new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + if (mInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView( + entry, HeadsUpController.this::showAlertingView); } + } - @Override - public void onEntryRemoved( - NotificationEntry entry, - NotificationVisibility visibility, - boolean removedByUser, - int reason) { - stopAlerting(entry.getKey()); - } - }); - } + @Override + public void onEntryUpdated(NotificationEntry entry) { + updateHunState(entry); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, int reason) { + stopAlerting(entry); + } + + @Override + public void onEntryCleanUp(NotificationEntry entry) { + mHeadsUpViewBinder.abortBindCallback(entry); + } + }; /** - * Adds the entry to the respective alerting manager if the content view was inflated and - * the entry should still alert. + * Adds the entry to the HUN manager and show it for the first time. */ private void showAlertingView(NotificationEntry entry) { - // TODO: Instead of this back and forth, we should listen to changes in heads up and - // cancel on-going heads up view inflation using the bind pipeline. - if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { - mHeadsUpManager.showNotification(entry); - if (!mStatusBarStateController.isDozing()) { - // Mark as seen immediately - setNotificationShown(entry.getSbn()); - } + mHeadsUpManager.showNotification(entry); + if (!mStatusBarStateController.isDozing()) { + // Mark as seen immediately + setNotificationShown(entry.getSbn()); } } - private void updateAlertState(NotificationEntry entry) { - boolean alertAgain = alertAgain(entry, entry.getSbn().getNotification()); + private void updateHunState(NotificationEntry entry) { + boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); // includes check for whether this notification should be filtered: - boolean shouldAlert = mNotificationInterruptStateProvider.shouldHeadsUp(entry); - final boolean wasAlerting = mHeadsUpManager.isAlerting(entry.getKey()); - if (wasAlerting) { - if (shouldAlert) { - mHeadsUpManager.updateNotification(entry.getKey(), alertAgain); + boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry); + final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); + if (wasHeadsUp) { + if (shouldHeadsUp) { + mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { // We don't want this to be interrupting anymore, let's remove it mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */); } - } else if (shouldAlert && alertAgain) { - // This notification was updated to be alerting, show it! - mHeadsUpManager.showNotification(entry); + } else if (shouldHeadsUp && hunAgain) { + mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification); } } - /** - * Checks whether an update for a notification warrants an alert for the user. - * - * @param oldEntry the entry for this notification. - * @param newNotification the new notification for this entry. - * @return whether this notification should alert the user. - */ - public static boolean alertAgain( - NotificationEntry oldEntry, Notification newNotification) { - return oldEntry == null || !oldEntry.hasInterrupted() - || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; - } - private void setNotificationShown(StatusBarNotification n) { try { mNotificationListener.setNotificationsShown(new String[]{n.getKey()}); @@ -141,10 +141,11 @@ public class NotificationAlertingManager { } } - private void stopAlerting(final String key) { - // Attempt to remove notifications from their alert manager. + private void stopAlerting(NotificationEntry entry) { + // Attempt to remove notifications from their HUN manager. // Though the remove itself may fail, it lets the manager know to remove as soon as // possible. + String key = entry.getKey(); if (mHeadsUpManager.isAlerting(key)) { // A cancel() in response to a remote input shouldn't be delayed, as it makes the // sending look longer than it takes. @@ -157,4 +158,28 @@ public class NotificationAlertingManager { mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); } } + + /** + * Checks whether an update for a notification warrants an alert for the user. + * + * @param oldEntry the entry for this notification. + * @param newNotification the new notification for this entry. + * @return whether this notification should alert the user. + */ + public static boolean alertAgain( + NotificationEntry oldEntry, Notification newNotification) { + return oldEntry == null || !oldEntry.hasInterrupted() + || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; + } + + private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) { + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry); + } + } + }; + + private static final String TAG = "HeadsUpBindController"; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java index 37acfa8dc0a4..ff139957031a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.headsup; +package com.android.systemui.statusbar.notification.interruption; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; @@ -42,7 +42,7 @@ import javax.inject.Singleton; * content view. * * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated - * (i.e. when {@link HeadsUpBindController} is removed). + * (i.e. when {@link HeadsUpController} is removed). */ @Singleton public class HeadsUpViewBinder { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index a27199370b16..55a593541819 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.notification.row; import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.provider.Settings.Global.NOTIFICATION_BUBBLES; import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; @@ -33,6 +35,7 @@ import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; +import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -43,6 +46,7 @@ import android.graphics.drawable.Icon; import android.os.Handler; import android.os.Parcelable; import android.os.RemoteException; +import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.transition.ChangeBounds; @@ -51,6 +55,7 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.AttributeSet; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; @@ -60,6 +65,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.Dependency; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationChannelHelper; import com.android.systemui.statusbar.notification.VisualStabilityManager; @@ -68,6 +74,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.annotation.Retention; import java.util.List; +import javax.inject.Provider; + /** * The guts of a conversation notification revealed when performing a long press. */ @@ -90,7 +98,11 @@ public class NotificationConversationInfo extends LinearLayout implements private ShortcutInfo mShortcutInfo; private String mConversationId; private StatusBarNotification mSbn; + private Notification.BubbleMetadata mBubbleMetadata; + private Context mUserContext; + private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider; private boolean mIsDeviceProvisioned; + private int mAppBubble; private TextView mPriorityDescriptionView; private TextView mDefaultDescriptionView; @@ -132,17 +144,17 @@ public class NotificationConversationInfo extends LinearLayout implements */ private OnClickListener mOnFavoriteClick = v -> { - mSelectedAction = ACTION_FAVORITE; + setSelectedAction(ACTION_FAVORITE); updateToggleActions(mSelectedAction, true); }; private OnClickListener mOnDefaultClick = v -> { - mSelectedAction = ACTION_DEFAULT; + setSelectedAction(ACTION_DEFAULT); updateToggleActions(mSelectedAction, true); }; private OnClickListener mOnMuteClick = v -> { - mSelectedAction = ACTION_MUTE; + setSelectedAction(ACTION_MUTE); updateToggleActions(mSelectedAction, true); }; @@ -166,6 +178,23 @@ public class NotificationConversationInfo extends LinearLayout implements void onClick(View v, int hoursToSnooze); } + @VisibleForTesting + void setSelectedAction(int selectedAction) { + if (mSelectedAction == selectedAction) { + return; + } + + mSelectedAction = selectedAction; + onSelectedActionChanged(); + } + + private void onSelectedActionChanged() { + // If the user selected Priority, maybe show the priority onboarding + if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) { + showPriorityOnboarding(); + } + } + public void bindNotification( ShortcutManager shortcutManager, PackageManager pm, @@ -177,6 +206,8 @@ public class NotificationConversationInfo extends LinearLayout implements OnSettingsClickListener onSettingsClick, OnSnoozeClickListener onSnoozeClickListener, ConversationIconFactory conversationIconFactory, + Context userContext, + Provider<PriorityOnboardingDialogController.Builder> builderProvider, boolean isDeviceProvisioned) { mSelectedAction = -1; mINotificationManager = iNotificationManager; @@ -192,6 +223,9 @@ public class NotificationConversationInfo extends LinearLayout implements mIsDeviceProvisioned = isDeviceProvisioned; mOnSnoozeClickListener = onSnoozeClickListener; mIconFactory = conversationIconFactory; + mUserContext = userContext; + mBubbleMetadata = entry.getBubbleMetadata(); + mBuilderProvider = builderProvider; mShortcutManager = shortcutManager; mConversationId = mNotificationChannel.getConversationId(); @@ -206,6 +240,13 @@ public class NotificationConversationInfo extends LinearLayout implements mNotificationChannel = NotificationChannelHelper.createConversationChannelIfNeeded( getContext(), mINotificationManager, entry, mNotificationChannel); + try { + mAppBubble = mINotificationManager.getBubblePreferenceForPackage(mPackageName, mAppUid); + } catch (RemoteException e) { + Log.e(TAG, "can't reach OS", e); + mAppBubble = BUBBLE_PREFERENCE_SELECTED; + } + bindHeader(); bindActions(); @@ -227,6 +268,11 @@ public class NotificationConversationInfo extends LinearLayout implements snooze.setOnClickListener(mOnSnoozeClick); */ + if (mAppBubble == BUBBLE_PREFERENCE_ALL) { + ((TextView) findViewById(R.id.default_summary)).setText(getResources().getString( + R.string.notification_channel_summary_default_with_bubbles, mAppName)); + } + findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick); findViewById(R.id.default_behavior).setOnClickListener(mOnDefaultClick); findViewById(R.id.silence).setOnClickListener(mOnMuteClick); @@ -264,7 +310,6 @@ public class NotificationConversationInfo extends LinearLayout implements // bindName(); bindPackage(); bindIcon(mNotificationChannel.isImportantConversation()); - } private void bindIcon(boolean important) { @@ -476,6 +521,38 @@ public class NotificationConversationInfo extends LinearLayout implements mAppUid, mSelectedAction, mNotificationChannel)); } + private boolean shouldShowPriorityOnboarding() { + return !Prefs.getBoolean(mUserContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, false); + } + + private void showPriorityOnboarding() { + View onboardingView = LayoutInflater.from(mContext) + .inflate(R.layout.priority_onboarding_half_shell, null); + + boolean ignoreDnd = false; + try { + ignoreDnd = (mINotificationManager + .getConsolidatedNotificationPolicy().priorityConversationSenders + & NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT) != 0; + } catch (RemoteException e) { + Log.e(TAG, "Could not check conversation senders", e); + } + + boolean showAsBubble = mBubbleMetadata.getAutoExpandBubble() + && Settings.Global.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, 0) == 1; + + PriorityOnboardingDialogController controller = mBuilderProvider.get() + .setContext(mUserContext) + .setView(onboardingView) + .setIgnoresDnd(ignoreDnd) + .setShowsAsBubble(showAsBubble) + .build(); + + controller.init(); + controller.show(); + } + /** * Closes the controls and commits the updated importance values (indirectly). * @@ -560,10 +637,7 @@ public class NotificationConversationInfo extends LinearLayout implements !mChannelToUpdate.isImportantConversation()); if (mChannelToUpdate.isImportantConversation()) { mChannelToUpdate.setAllowBubbles(true); - int currentPref = - mINotificationManager.getBubblePreferenceForPackage( - mAppPkg, mAppUid); - if (currentPref == BUBBLE_PREFERENCE_NONE) { + if (mAppBubble == BUBBLE_PREFERENCE_NONE) { mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid, BUBBLE_PREFERENCE_SELECTED); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 2487d1a898a3..624fabc0a496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -49,6 +49,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.CurrentUserContextTracker; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -67,6 +68,8 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.FileDescriptor; import java.io.PrintWriter; +import javax.inject.Provider; + import dagger.Lazy; /** @@ -111,6 +114,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final INotificationManager mNotificationManager; private final LauncherApps mLauncherApps; private final ShortcutManager mShortcutManager; + private final CurrentUserContextTracker mContextTracker; + private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider; /** * Injected constructor. See {@link NotificationsModule}. @@ -121,7 +126,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, LauncherApps launcherApps, - ShortcutManager shortcutManager) { + ShortcutManager shortcutManager, + CurrentUserContextTracker contextTracker, + Provider<PriorityOnboardingDialogController.Builder> builderProvider) { mContext = context; mVisualStabilityManager = visualStabilityManager; mStatusBarLazy = statusBarLazy; @@ -131,6 +138,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mNotificationManager = notificationManager; mLauncherApps = launcherApps; mShortcutManager = shortcutManager; + mContextTracker = contextTracker; + mBuilderProvider = builderProvider; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -403,6 +412,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx onSettingsClick, onSnoozeClickListener, iconFactoryLoader, + mContextTracker.getCurrentUserContext(), + mBuilderProvider, mDeviceProvisionedController.isDeviceProvisioned()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt new file mode 100644 index 000000000000..d1b405256f39 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.Dialog +import android.content.Context +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.drawable.ColorDrawable +import android.view.Gravity +import android.view.View +import android.view.View.GONE +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.Window +import android.view.WindowInsets.Type.statusBars +import android.view.WindowManager +import android.widget.LinearLayout +import android.widget.TextView +import com.android.systemui.Prefs +import com.android.systemui.R +import java.lang.IllegalStateException +import javax.inject.Inject + +/** + * Controller to handle presenting the priority conversations onboarding dialog + */ +class PriorityOnboardingDialogController @Inject constructor( + val view: View, + val context: Context, + val ignoresDnd: Boolean, + val showsAsBubble: Boolean +) { + + private lateinit var dialog: Dialog + + fun init() { + initDialog() + } + + fun show() { + dialog.show() + } + + private fun done() { + // Log that the user has seen the onboarding + Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true) + dialog.dismiss() + } + + class Builder @Inject constructor() { + private lateinit var view: View + private lateinit var context: Context + private var ignoresDnd = false + private var showAsBubble = false + + fun setView(v: View): Builder { + view = v + return this + } + + fun setContext(c: Context): Builder { + context = c + return this + } + + fun setIgnoresDnd(ignore: Boolean): Builder { + ignoresDnd = ignore + return this + } + + fun setShowsAsBubble(bubble: Boolean): Builder { + showAsBubble = bubble + return this + } + + fun build(): PriorityOnboardingDialogController { + val controller = PriorityOnboardingDialogController( + view, context, ignoresDnd, showAsBubble) + return controller + } + } + + private fun initDialog() { + dialog = Dialog(context) + + if (dialog.window == null) { + throw IllegalStateException("Need a window for the onboarding dialog to show") + } + + dialog.window?.requestFeature(Window.FEATURE_NO_TITLE) + // Prevent a11y readers from reading the first element in the dialog twice + dialog.setTitle("\u00A0") + dialog.apply { + setContentView(view) + setCanceledOnTouchOutside(true) + + findViewById<TextView>(R.id.done_button)?.setOnClickListener { + done() + } + + if (!ignoresDnd) { + findViewById<LinearLayout>(R.id.ignore_dnd_tip).visibility = GONE + } + + if (!showsAsBubble) { + findViewById<LinearLayout>(R.id.floating_bubble_tip).visibility = GONE + } + + window?.apply { + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + addFlags(wmFlags) + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + setWindowAnimations(com.android.internal.R.style.Animation_InputMethod) + + attributes = attributes.apply { + format = PixelFormat.TRANSLUCENT + title = ChannelEditorDialogController::class.java.simpleName + gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv() + width = MATCH_PARENT + height = WRAP_CONTENT + } + } + } + } + + private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS + or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 7ac066277c86..e20be2b71939 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.row.wrapper; import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; -import android.annotation.NonNull; import android.app.AppOpsManager; import android.app.Notification; import android.content.Context; @@ -133,15 +132,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { if (mAppOps != null) { mAppOps.setOnClickListener(listener); } - if (mCameraIcon != null) { - mCameraIcon.setOnClickListener(listener); - } - if (mMicIcon != null) { - mMicIcon.setOnClickListener(listener); - } - if (mOverlayIcon != null) { - mOverlayIcon.setOnClickListener(listener); - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 874d81db0bd2..2da2724aacb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -193,7 +193,8 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi mBackgroundColor, mActions, compactActions, - notif.contentIntent); + notif.contentIntent, + sbn.getKey()); QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById( com.android.systemui.R.id.quick_settings_panel); bigPanel.addMediaSession(token, @@ -201,7 +202,8 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi tintColor, mBackgroundColor, mActions, - sbn); + sbn, + sbn.getKey()); } boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index f103bd01fc3f..7d422e3c15a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -312,7 +312,7 @@ public class EdgeBackGestureHandler implements DisplayListener, WindowManagerGlobal.getWindowManagerService() .unregisterSystemGestureExclusionListener( mGestureExclusionListener, mDisplayId); - } catch (RemoteException e) { + } catch (RemoteException | IllegalArgumentException e) { Log.e(TAG, "Failed to unregister window manager callbacks", e); } @@ -326,7 +326,7 @@ public class EdgeBackGestureHandler implements DisplayListener, WindowManagerGlobal.getWindowManagerService() .registerSystemGestureExclusionListener( mGestureExclusionListener, mDisplayId); - } catch (RemoteException e) { + } catch (RemoteException | IllegalArgumentException e) { Log.e(TAG, "Failed to register window manager callbacks", e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 31797d1faa61..c9716d39590e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1780,7 +1780,13 @@ public class NotificationPanelViewController extends PanelViewController { }); animator.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationStart(Animator animation) { + notifyExpandingStarted(); + } + + @Override public void onAnimationEnd(Animator animation) { + notifyExpandingFinished(); mNotificationStackScroller.resetCheckSnoozeLeavebehind(); mQsExpansionAnimator = null; if (onFinishRunnable != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index f7d403f667cb..81dc9e1cf0e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -157,7 +157,7 @@ public abstract class PanelViewController { protected void onExpandingStarted() { } - private void notifyExpandingStarted() { + protected void notifyExpandingStarted() { if (!mExpanding) { mExpanding = true; onExpandingStarted(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 900b3ea97010..ca65665d4ae5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -193,7 +193,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -623,7 +622,6 @@ public class StatusBar extends SystemUI implements DemoMode, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, - NotificationAlertingManager notificationAlertingManager, // need to inject for now DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index b81a5198b498..02e031217904 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -62,7 +62,6 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -143,7 +142,6 @@ public interface StatusBarPhoneModule { NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, - NotificationAlertingManager notificationAlertingManager, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, @UiBackground Executor uiBgExecutor, @@ -223,7 +221,6 @@ public interface StatusBarPhoneModule { notificationInterruptStateProvider, notificationViewHierarchyManager, keyguardViewMediator, - notificationAlertingManager, displayMetrics, metricsLogger, uiBgExecutor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 6e5f8a0ae5e9..051bd29bc323 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -367,7 +367,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mobileSignalController.unregisterListener(); } mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener); - mContext.unregisterReceiver(this); + mBroadcastDispatcher.unregisterReceiver(this); } public int getConnectedWifiLevel() { @@ -859,6 +859,7 @@ public class NetworkControllerImpl extends BroadcastReceiver pw.println(" - telephony ------"); pw.print(" hasVoiceCallingFeature()="); pw.println(hasVoiceCallingFeature()); + pw.println(" mListening=" + mListening); pw.println(" - connectivity ------"); pw.print(" mConnectedTransports="); diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt index ca4b67db0d46..242f7cde9d3b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt @@ -187,16 +187,23 @@ class FloatingContentCoordinator @Inject constructor() { // Tell that content to get out of the way, and save the bounds it says it's moving // (or animating) to. .forEach { (content, bounds) -> - content.moveToBounds( - content.calculateNewBoundsOnOverlap( - conflictingNewBounds, - // Pass all of the content bounds except the bounds of the - // content we're asking to move, and the conflicting new bounds - // (since those are passed separately). - otherContentBounds = allContentBounds.values - .minus(bounds) - .minus(conflictingNewBounds))) - allContentBounds[content] = content.getFloatingBoundsOnScreen() + val newBounds = content.calculateNewBoundsOnOverlap( + conflictingNewBounds, + // Pass all of the content bounds except the bounds of the + // content we're asking to move, and the conflicting new bounds + // (since those are passed separately). + otherContentBounds = allContentBounds.values + .minus(bounds) + .minus(conflictingNewBounds)) + + // If the new bounds are empty, it means there's no non-overlapping position + // that is in bounds. Just leave the content where it is. This should normally + // not happen, but sometimes content like PIP reports incorrect bounds + // temporarily. + if (!newBounds.isEmpty) { + content.moveToBounds(newBounds) + allContentBounds[content] = content.getFloatingBoundsOnScreen() + } } currentlyResolvingConflicts = false @@ -229,8 +236,8 @@ class FloatingContentCoordinator @Inject constructor() { * @param allowedBounds The area within which we're allowed to find new bounds for the * content. * @return New bounds for the content that don't intersect the exclusion rects or the - * newly overlapping rect, and that is within bounds unless no possible in-bounds position - * exists. + * newly overlapping rect, and that is within bounds - or an empty Rect if no in-bounds + * position exists. */ @JvmStatic fun findAreaForContentVertically( @@ -274,7 +281,13 @@ class FloatingContentCoordinator @Inject constructor() { !overlappingContentPushingDown && !positionAboveInBounds // Return the content rect, but offset to reflect the new position. - return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove + val newBounds = if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove + + // If the new bounds are within the allowed bounds, return them. If not, it means that + // there are no legal new bounds. This can happen if the new content's bounds are too + // large (for example, full-screen PIP). Since there is no reasonable action to take + // here, return an empty Rect and we will just not move the content. + return if (allowedBounds.contains(newBounds)) newBounds else Rect() } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index f27bdbfbeda0..e905e6772074 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -27,6 +27,7 @@ import android.provider.Settings import android.view.MotionEvent import android.view.VelocityTracker import android.view.View +import android.view.ViewConfiguration import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringForce @@ -146,6 +147,10 @@ abstract class MagnetizedObject<T : Any>( private val velocityTracker: VelocityTracker = VelocityTracker.obtain() private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + private var touchDown = PointF() + private var touchSlop = 0 + private var movedBeyondSlop = false + /** Whether touch events are presently occurring within the magnetic field area of a target. */ val objectStuckToTarget: Boolean get() = targetObjectIsStuckTo != null @@ -324,15 +329,32 @@ abstract class MagnetizedObject<T : Any>( // When a gesture begins, recalculate target views' positions on the screen in case they // have changed. Also, clear state. if (ev.action == MotionEvent.ACTION_DOWN) { - updateTargetViewLocations() + updateTargetViews() - // Clear the velocity tracker and assume we're not stuck to a target yet. + // Clear the velocity tracker and stuck target. velocityTracker.clear() targetObjectIsStuckTo = null + + // Set the touch down coordinates and reset movedBeyondSlop. + touchDown.set(ev.rawX, ev.rawY) + movedBeyondSlop = false } + // Always pass events to the VelocityTracker. addMovement(ev) + // If we haven't yet moved beyond the slop distance, check if we have. + if (!movedBeyondSlop) { + val dragDistance = hypot(ev.rawX - touchDown.x, ev.rawY - touchDown.y) + if (dragDistance > touchSlop) { + // If we're beyond the slop distance, save that and continue. + movedBeyondSlop = true + } else { + // Otherwise, don't do anything yet. + return false + } + } + val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target -> val distanceFromTargetCenter = hypot( ev.rawX - target.centerOnScreen.x, @@ -559,8 +581,14 @@ abstract class MagnetizedObject<T : Any>( } /** Updates the locations on screen of all of the [associatedTargets]. */ - internal fun updateTargetViewLocations() { + internal fun updateTargetViews() { associatedTargets.forEach { it.updateLocationOnScreen() } + + // Update the touch slop, since the configuration may have changed. + if (associatedTargets.size > 0) { + touchSlop = + ViewConfiguration.get(associatedTargets[0].targetView.context).scaledTouchSlop + } } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java index 5227aaf01249..0c69ffdef372 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -16,7 +16,6 @@ package com.android.systemui; -import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.doReturn; @@ -32,19 +31,16 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.ColorSpace; -import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.Size; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceHolder; import com.android.systemui.glwallpaper.ImageWallpaperRenderer; -import com.android.systemui.statusbar.phone.DozeParameters; import org.junit.Before; import org.junit.Test; @@ -72,8 +68,6 @@ public class ImageWallpaperTest extends SysuiTestCase { @Mock private Bitmap mWallpaperBitmap; @Mock - private DozeParameters mDozeParam; - @Mock private Handler mHandler; private CountDownLatch mEventCountdown; @@ -100,14 +94,13 @@ public class ImageWallpaperTest extends SysuiTestCase { when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap); when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB)); when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888); - when(mDozeParam.getDisplayNeedsBlanking()).thenReturn(false); } private ImageWallpaper createImageWallpaper() { - return new ImageWallpaper(mDozeParam) { + return new ImageWallpaper() { @Override public Engine onCreateEngine() { - return new GLEngine(mDozeParam, mHandler) { + return new GLEngine(mHandler) { @Override public Context getDisplayContext() { return mMockContext; @@ -130,75 +123,52 @@ public class ImageWallpaperTest extends SysuiTestCase { }; } - private ImageWallpaperRenderer createImageWallpaperRenderer(ImageWallpaper.GLEngine engine) { - return new ImageWallpaperRenderer(mMockContext, engine) { - @Override - public void startProcessingImage() { - // No - Op - } - }; - } - @Test public void testBitmapWallpaper_normal() { // Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH. // Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH. - // Finally, we assert the transition will not be stopped. - verifySurfaceSizeAndAssertTransition(DISPLAY_WIDTH /* bmpWidth */, + verifySurfaceSize(DISPLAY_WIDTH /* bmpWidth */, DISPLAY_WIDTH /* bmpHeight */, DISPLAY_WIDTH /* surfaceWidth */, - DISPLAY_WIDTH /* surfaceHeight */, - false /* assertion */); + DISPLAY_WIDTH /* surfaceHeight */); } @Test public void testBitmapWallpaper_low_resolution() { // Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT. // Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT. - // Finally, we assert the transition will be stopped. - verifySurfaceSizeAndAssertTransition(LOW_BMP_WIDTH /* bmpWidth */, + verifySurfaceSize(LOW_BMP_WIDTH /* bmpWidth */, LOW_BMP_HEIGHT /* bmpHeight */, LOW_BMP_WIDTH /* surfaceWidth */, - LOW_BMP_HEIGHT /* surfaceHeight */, - false /* assertion */); + LOW_BMP_HEIGHT /* surfaceHeight */); } @Test public void testBitmapWallpaper_too_small() { // Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT. // Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT. - // Finally, we assert the transition will be stopped. - verifySurfaceSizeAndAssertTransition(INVALID_BMP_WIDTH /* bmpWidth */, + verifySurfaceSize(INVALID_BMP_WIDTH /* bmpWidth */, INVALID_BMP_HEIGHT /* bmpHeight */, ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */, - ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */, - false /* assertion */); + ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */); } - private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight, - int surfaceWidth, int surfaceHeight, boolean assertion) { + private void verifySurfaceSize(int bmpWidth, int bmpHeight, + int surfaceWidth, int surfaceHeight) { ImageWallpaper.GLEngine wallpaperEngine = (ImageWallpaper.GLEngine) createImageWallpaper().onCreateEngine(); ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine); - when(engineSpy.mIsHighEndGfx).thenReturn(true); when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth); when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight); - ImageWallpaperRenderer renderer = createImageWallpaperRenderer(engineSpy); + ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext); doReturn(renderer).when(engineSpy).getRendererInstance(); engineSpy.onCreate(engineSpy.getSurfaceHolder()); verify(mSurfaceHolder, times(1)).setFixedSize(surfaceWidth, surfaceHeight); assertWithMessage("setFixedSizeAllowed should have been called.").that( mEventCountdown.getCount()).isEqualTo(0); - - Size frameSize = renderer.reportSurfaceSize(); - Rect frame = new Rect(0, 0, frameSize.getWidth(), frameSize.getHeight()); - when(mSurfaceHolder.getSurfaceFrame()).thenReturn(frame); - - assertThat(engineSpy.checkIfShouldStopTransition()).isEqualTo(assertion); - // destroy } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java index 4b47093bb951..e23507b0a895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java @@ -40,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -84,22 +85,17 @@ public class EglHelperTest extends SysuiTestCase { } @Test - public void testInit_finish() { + public void testInit_normal() { mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */); assertThat(mEglHelper.hasEglDisplay()).isTrue(); assertThat(mEglHelper.hasEglContext()).isTrue(); assertThat(mEglHelper.hasEglSurface()).isTrue(); verify(mEglHelper).askCreatingEglWindowSurface( any(SurfaceHolder.class), eq(null), anyInt()); - - mEglHelper.finish(); - assertThat(mEglHelper.hasEglSurface()).isFalse(); - assertThat(mEglHelper.hasEglContext()).isFalse(); - assertThat(mEglHelper.hasEglDisplay()).isFalse(); } @Test - public void testInit_finish_wide_gamut() { + public void testInit_wide_gamut() { // In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490; doReturn(0x3490).when(mEglHelper).getWcgCapability(); // In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace"; @@ -113,10 +109,10 @@ public class EglHelperTest extends SysuiTestCase { .askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt()); assertThat(ac.getValue()).isNotNull(); assertThat(ac.getValue()).isEqualTo(expectedArgument); - mEglHelper.finish(); } @Test + @Ignore public void testFinish_shouldNotCrash() { mEglHelper.terminateEglDisplay(); assertThat(mEglHelper.hasEglDisplay()).isFalse(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageRevealHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageRevealHelperTest.java deleted file mode 100644 index c827ac7ab963..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageRevealHelperTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.glwallpaper; - -import static com.google.common.truth.Truth.assertThat; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class ImageRevealHelperTest extends SysuiTestCase { - - static final int ANIMATION_DURATION = 500; - ImageRevealHelper mImageRevealHelper; - ImageRevealHelper.RevealStateListener mRevealStateListener; - - @Before - public void setUp() throws Exception { - mRevealStateListener = new ImageRevealHelper.RevealStateListener() { - @Override - public void onRevealStateChanged() { - // no-op - } - - @Override - public void onRevealStart(boolean animate) { - // no-op - } - - @Override - public void onRevealEnd() { - // no-op - } - }; - mImageRevealHelper = new ImageRevealHelper(mRevealStateListener); - } - - @Test - public void testBiometricAuthUnlockAnimateImageRevealState_shouldNotBlackoutScreen() { - assertThat(mImageRevealHelper.getReveal()).isEqualTo(0f); - - mImageRevealHelper.updateAwake(true /* awake */, ANIMATION_DURATION); - assertThat(mImageRevealHelper.getReveal()).isEqualTo(0f); - - // When device unlock through Biometric, should not show reveal transition - mImageRevealHelper.updateAwake(false /* awake */, 0); - assertThat(mImageRevealHelper.getReveal()).isEqualTo(1f); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java index d61be37692c4..24f3eb27579a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java @@ -16,8 +16,6 @@ package com.android.systemui.glwallpaper; -import static com.android.systemui.glwallpaper.GLWallpaperRenderer.SurfaceProxy; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; @@ -48,30 +46,12 @@ import java.util.Set; public class ImageWallpaperRendererTest extends SysuiTestCase { private WallpaperManager mWpmSpy; - private SurfaceProxy mSurfaceProxy; @Before public void setUp() throws Exception { final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class); mWpmSpy = spy(wpm); mContext.addMockSystemService(WallpaperManager.class, mWpmSpy); - - mSurfaceProxy = new SurfaceProxy() { - @Override - public void requestRender() { - // NO-op - } - - @Override - public void preRender() { - // No-op - } - - @Override - public void postRender() { - // No-op - } - }; } @Test @@ -91,12 +71,12 @@ public class ImageWallpaperRendererTest extends SysuiTestCase { doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces(); mWpmSpy.setBitmap(p3Bitmap); - ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext); rendererP3.reportSurfaceSize(); assertThat(rendererP3.isWcgContent()).isTrue(); mWpmSpy.setBitmap(srgbBitmap); - ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext); assertThat(renderer.isWcgContent()).isFalse(); } finally { srgbBitmap.recycle(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt index f316d0480fac..28a3d6a32a61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -86,7 +86,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test - fun updateDuration() { + fun updateDurationWithPlayback() { // GIVEN that the duration is contained within the metadata val duration = 12000L val metadata = MediaMetadata.Builder().run { @@ -94,6 +94,12 @@ public class SeekBarViewModelTest : SysuiTestCase() { build() } whenever(mockController.getMetadata()).thenReturn(metadata) + // AND a valid playback state (ie. media session is not destroyed) + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController, Color.RED) // THEN the duration is extracted @@ -102,6 +108,22 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + fun updateDurationWithoutPlayback() { + // GIVEN that the duration is contained within the metadata + val duration = 12000L + val metadata = MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } + whenever(mockController.getMetadata()).thenReturn(metadata) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN the duration is extracted + assertThat(viewModel.progress.value!!.duration).isEqualTo(duration) + assertThat(viewModel.progress.value!!.enabled).isFalse() + } + + @Test fun updateDurationNegative() { // GIVEN that the duration is negative val duration = -1L @@ -110,6 +132,12 @@ public class SeekBarViewModelTest : SysuiTestCase() { build() } whenever(mockController.getMetadata()).thenReturn(metadata) + // AND a valid playback state (ie. media session is not destroyed) + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController, Color.RED) // THEN the seek bar is disabled @@ -125,6 +153,12 @@ public class SeekBarViewModelTest : SysuiTestCase() { build() } whenever(mockController.getMetadata()).thenReturn(metadata) + // AND a valid playback state (ie. media session is not destroyed) + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController, Color.RED) // THEN the seek bar is disabled @@ -372,4 +406,30 @@ public class SeekBarViewModelTest : SysuiTestCase() { // THEN an update task is queued assertThat(fakeExecutor.numPending()).isEqualTo(1) } + + @Test + fun clearSeekBar() { + // GIVEN that the duration is contained within the metadata + val metadata = MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L) + build() + } + whenever(mockController.getMetadata()).thenReturn(metadata) + // AND a valid playback state (ie. media session is not destroyed) + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // AND the controller has been updated + viewModel.updateController(mockController, Color.RED) + // WHEN the controller is cleared on the event when the session is destroyed + viewModel.clearController() + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // THEN the seek bar is disabled + assertThat(viewModel.progress.value!!.enabled).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java index f3038ce051cd..730481afe638 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java @@ -39,7 +39,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder; +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; import com.android.systemui.statusbar.policy.HeadsUpManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index b6bd5e213dd4..61388b6d0389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -16,13 +16,12 @@ package com.android.systemui.statusbar.notification.row; -import static android.app.Notification.FLAG_BUBBLE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME; -import static android.provider.Settings.Global.NOTIFICATION_BUBBLES; -import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -36,8 +35,10 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -49,6 +50,7 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.PendingIntent; import android.app.Person; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; @@ -59,20 +61,19 @@ import android.content.pm.ShortcutManager; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.UserHandle; -import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.Dependency; +import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleController; @@ -87,6 +88,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; @@ -97,6 +99,8 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; +import javax.inject.Provider; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -143,6 +147,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase { private ShadeController mShadeController; @Mock private ConversationIconFactory mIconFactory; + @Mock + private Context mUserContext; + @Mock(answer = Answers.RETURNS_SELF) + private PriorityOnboardingDialogController.Builder mBuilder; + private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider = () -> mBuilder; @Before public void setUp() throws Exception { @@ -234,6 +243,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon); assertEquals(mIconDrawable, view.getDrawable()); @@ -253,6 +264,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); assertTrue(textView.getText().toString().contains("App Name")); @@ -298,6 +311,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final TextView textView = mNotificationInfo.findViewById(R.id.group_name); assertTrue(textView.getText().toString().contains(group.getName())); @@ -319,6 +334,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final TextView textView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -339,6 +356,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); @@ -366,6 +385,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); @@ -389,6 +410,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { }, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); @@ -410,6 +433,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); @@ -432,6 +457,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { }, null, mIconFactory, + mUserContext, + mBuilderProvider, false); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); @@ -452,13 +479,45 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View view = mNotificationInfo.findViewById(R.id.silence); assertThat(view.isSelected()).isTrue(); } @Test - public void testBindNotification_defaultSelected_notFave_notSilent() { + public void testBindNotification_defaultSelected_notFave_notSilent() throws Exception { + when(mMockINotificationManager.getBubblePreferenceForPackage(anyString(), anyInt())) + .thenReturn(BUBBLE_PREFERENCE_SELECTED); + mConversationChannel.setImportance(IMPORTANCE_HIGH); + mConversationChannel.setImportantConversation(false); + mConversationChannel.setAllowBubbles(true); + mNotificationInfo.bindNotification( + mShortcutManager, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + mIconFactory, + mUserContext, + mBuilderProvider, + true); + View view = mNotificationInfo.findViewById(R.id.default_behavior); + assertThat(view.isSelected()).isTrue(); + assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo( + mContext.getString(R.string.notification_channel_summary_default)); + } + + @Test + public void testBindNotification_default_allCanBubble() throws Exception { + when(mMockINotificationManager.getBubblePreferenceForPackage(anyString(), anyInt())) + .thenReturn(BUBBLE_PREFERENCE_ALL); + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mConversationChannel.setImportance(IMPORTANCE_HIGH); mConversationChannel.setImportantConversation(false); mConversationChannel.setAllowBubbles(true); @@ -473,9 +532,14 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View view = mNotificationInfo.findViewById(R.id.default_behavior); assertThat(view.isSelected()).isTrue(); + assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo( + mContext.getString(R.string.notification_channel_summary_default_with_bubbles, + "App Name")); } @Test @@ -495,6 +559,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View fave = mNotificationInfo.findViewById(R.id.priority); @@ -533,6 +599,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); @@ -570,6 +638,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View silence = mNotificationInfo.findViewById(R.id.silence); @@ -608,6 +678,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View fave = mNotificationInfo.findViewById(R.id.priority); @@ -640,6 +712,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View fave = mNotificationInfo.findViewById(R.id.priority); @@ -670,6 +744,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); @@ -701,6 +777,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); @@ -732,6 +810,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); @@ -762,6 +842,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); View silence = mNotificationInfo.findViewById(R.id.silence); @@ -791,6 +873,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage( @@ -811,9 +895,81 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, null, mIconFactory, + mUserContext, + mBuilderProvider, true); verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); } + + @Test + public void testSelectPriorityPresentsOnboarding_firstTime() { + // GIVEN pref is false + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, false); + + // GIVEN the priority onboarding screen is present + PriorityOnboardingDialogController.Builder b = + new PriorityOnboardingDialogController.Builder(); + PriorityOnboardingDialogController controller = + mock(PriorityOnboardingDialogController.class); + when(b.build()).thenReturn(controller); + + // GIVEN the user is changing conversation settings + when(mBuilderProvider.get()).thenReturn(b); + mNotificationInfo.bindNotification( + mShortcutManager, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + mIconFactory, + mUserContext, + mBuilderProvider, + true); + + // WHEN user clicks "priority" + mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); + + // THEN the user is presented with the priority onboarding screen + verify(controller, atLeastOnce()).show(); + } + + @Test + public void testSelectPriorityDoesNotShowOnboarding_secondTime() { + //WHEN pref is true + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true); + + PriorityOnboardingDialogController.Builder b = + new PriorityOnboardingDialogController.Builder(); + PriorityOnboardingDialogController controller = + mock(PriorityOnboardingDialogController.class); + when(b.build()).thenReturn(controller); + + when(mBuilderProvider.get()).thenReturn(b); + mNotificationInfo.bindNotification( + mShortcutManager, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + mIconFactory, + mUserContext, + mBuilderProvider, + true); + + // WHEN user clicks "priority" + mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); + + // THEN the user is presented with the priority onboarding screen + verify(controller, never()).show(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index ed4642344dba..5813740712b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -66,6 +66,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.settings.CurrentUserContextTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -83,11 +84,14 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import javax.inject.Provider; + /** * Tests for {@link NotificationGutsManager}. */ @@ -120,6 +124,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private LauncherApps mLauncherApps; @Mock private ShortcutManager mShortcutManager; @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; + @Mock private CurrentUserContextTracker mContextTracker; + @Mock(answer = Answers.RETURNS_SELF) + private PriorityOnboardingDialogController.Builder mBuilder; + private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder; @Before public void setUp() { @@ -136,7 +144,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider, - mINotificationManager, mLauncherApps, mShortcutManager); + mINotificationManager, mLauncherApps, mShortcutManager, mContextTracker, mProvider); mGutsManager.setUpWithPresenter(mPresenter, mStackScroller, mCheckSaveListener, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index b905bddb98f5..5a08c9ca017b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -120,7 +120,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake; @@ -193,7 +192,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StatusBarNotificationPresenter mNotificationPresenter; @Mock private NotificationEntryListener mEntryListener; @Mock private NotificationFilter mNotificationFilter; - @Mock private NotificationAlertingManager mNotificationAlertingManager; @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration; @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -352,7 +350,6 @@ public class StatusBarTest extends SysuiTestCase { mNotificationInterruptStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, - mNotificationAlertingManager, new DisplayMetrics(), mMetricsLogger, mUiBgExecutor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt index f6b7b74d4bfc..251ca9c8dcb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt @@ -186,8 +186,8 @@ class MagnetizedObjectTest : SysuiTestCase() { @Test fun testMotionEventConsumption_downInMagneticField() { - // We should consume DOWN events if they occur in the field. - assertTrue(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent( + // We should not consume DOWN events even if they occur in the field. + assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent( x = targetCenterX, y = targetCenterY, action = MotionEvent.ACTION_DOWN))) } @@ -342,10 +342,14 @@ class MagnetizedObjectTest : SysuiTestCase() { // Trigger the magnet animation, and block the test until it ends. PhysicsAnimatorTestUtils.setAllAnimationsBlock(true) magnetizedObject.maybeConsumeMotionEvent(getMotionEvent( - x = targetCenterX, - y = targetCenterY, + x = targetCenterX - 250, + y = targetCenterY - 250, action = MotionEvent.ACTION_DOWN)) + magnetizedObject.maybeConsumeMotionEvent(getMotionEvent( + x = targetCenterX, + y = targetCenterY)) + // The object's (top-left) position should now position it centered over the target. assertEquals(targetCenterX - objectSize / 2, objectX) assertEquals(targetCenterY - objectSize / 2, objectY) diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index 1dc8227e81f4..2b2fe4534c3e 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -34,11 +34,14 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <protected-broadcast android:name="com.android.server.connectivity.tethering.DISABLE_TETHERING" /> + <application android:process="com.android.networkstack.process" android:extractNativeLibs="false" diff --git a/packages/Tethering/res/values-mcc204-mnc04/strings.xml b/packages/Tethering/res/values-mcc204-mnc04/strings.xml deleted file mode 100644 index 9dadd49cf8a4..000000000000 --- a/packages/Tethering/res/values-mcc204-mnc04/strings.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- String for no upstream notification title [CHAR LIMIT=200] --> - <string name="no_upstream_notification_title">Tethering has no internet</string> - <!-- String for no upstream notification title [CHAR LIMIT=200] --> - <string name="no_upstream_notification_message">Devices can\u2019t connect</string> - <!-- String for no upstream notification disable button [CHAR LIMIT=200] --> - <string name="no_upstream_notification_disable_button">Turn off tethering</string> - - <!-- String for cellular roaming notification title [CHAR LIMIT=200] --> - <string name="upstream_roaming_notification_title">Hotspot or tethering is on</string> - <!-- String for cellular roaming notification message [CHAR LIMIT=500] --> - <string name="upstream_roaming_notification_message">Additional charges may apply while roaming</string> - <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] --> - <string name="upstream_roaming_notification_continue_button">Continue</string> -</resources> diff --git a/packages/Tethering/res/values-mcc310-mnc004/config.xml b/packages/Tethering/res/values-mcc310-mnc004/config.xml new file mode 100644 index 000000000000..8c627d5df058 --- /dev/null +++ b/packages/Tethering/res/values-mcc310-mnc004/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to + "0" for disable this feature. --> + <integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer> +</resources>
\ No newline at end of file diff --git a/packages/Tethering/res/values-mcc311-mnc480/config.xml b/packages/Tethering/res/values-mcc311-mnc480/config.xml new file mode 100644 index 000000000000..8c627d5df058 --- /dev/null +++ b/packages/Tethering/res/values-mcc311-mnc480/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to + "0" for disable this feature. --> + <integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer> +</resources>
\ No newline at end of file diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 430fdc42284d..52aa5bbaffa5 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -202,4 +202,10 @@ <string name="tethering_notification_title">@string/tethered_notification_title</string> <!-- String for tether enable notification message. --> <string name="tethering_notification_message">@string/tethered_notification_message</string> + + <!-- No upstream notification is shown when there is a downstream but no upstream that is able + to do the tethering. --> + <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to + "-1" for disable this feature. --> + <integer name="delay_to_show_no_upstream_after_no_backhaul">-1</integer> </resources> diff --git a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index 4c7b2d49ee9a..049a9f68bbd2 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -38,8 +38,6 @@ import android.content.IntentFilter; import android.net.util.SharedLog; import android.os.Bundle; import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; @@ -75,11 +73,6 @@ public class EntitlementManager { private final ComponentName mSilentProvisioningService; private static final int MS_PER_HOUR = 60 * 60 * 1000; - private static final int EVENT_START_PROVISIONING = 0; - private static final int EVENT_STOP_PROVISIONING = 1; - private static final int EVENT_UPSTREAM_CHANGED = 2; - private static final int EVENT_MAYBE_RUN_PROVISIONING = 3; - private static final int EVENT_GET_ENTITLEMENT_VALUE = 4; // The ArraySet contains enabled downstream types, ex: // {@link TetheringManager.TETHERING_WIFI} @@ -90,7 +83,7 @@ public class EntitlementManager { private final int mPermissionChangeMessageCode; private final SharedLog mLog; private final SparseIntArray mEntitlementCacheValue; - private final EntitlementHandler mHandler; + private final Handler mHandler; private final StateMachine mTetherMasterSM; // Key: TetheringManager.TETHERING_*(downstream). // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). @@ -112,10 +105,7 @@ public class EntitlementManager { mEntitlementCacheValue = new SparseIntArray(); mTetherMasterSM = tetherMasterSM; mPermissionChangeMessageCode = permissionChangeMessageCode; - final Handler masterHandler = tetherMasterSM.getHandler(); - // Create entitlement's own handler which is associated with TetherMaster thread - // let all entitlement processes run in the same thread. - mHandler = new EntitlementHandler(masterHandler.getLooper()); + mHandler = tetherMasterSM.getHandler(); mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), null, mHandler); mSilentProvisioningService = ComponentName.unflattenFromString( @@ -172,14 +162,9 @@ public class EntitlementManager { * provisioning app UI if there is one. */ public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_START_PROVISIONING, - downstreamType, encodeBool(showProvisioningUi))); - } - - private void handleStartProvisioningIfNeeded(int type, boolean showProvisioningUi) { - if (!isValidDownstreamType(type)) return; + if (!isValidDownstreamType(downstreamType)) return; - if (!mCurrentTethers.contains(type)) mCurrentTethers.add(type); + if (!mCurrentTethers.contains(downstreamType)) mCurrentTethers.add(downstreamType); final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); if (isTetherProvisioningRequired(config)) { @@ -192,9 +177,9 @@ public class EntitlementManager { // till upstream change to cellular. if (mUsingCellularAsUpstream) { if (showProvisioningUi) { - runUiTetherProvisioning(type, config.activeDataSubId); + runUiTetherProvisioning(downstreamType, config.activeDataSubId); } else { - runSilentTetherProvisioning(type, config.activeDataSubId); + runSilentTetherProvisioning(downstreamType, config.activeDataSubId); } mNeedReRunProvisioningUi = false; } else { @@ -211,10 +196,6 @@ public class EntitlementManager { * @param type tethering type from TetheringManager.TETHERING_{@code *} */ public void stopProvisioningIfNeeded(int type) { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0)); - } - - private void handleStopProvisioningIfNeeded(int type) { if (!isValidDownstreamType(type)) return; mCurrentTethers.remove(type); @@ -230,11 +211,6 @@ public class EntitlementManager { * @param isCellular whether tethering upstream is cellular. */ public void notifyUpstream(boolean isCellular) { - mHandler.sendMessage(mHandler.obtainMessage( - EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); - } - - private void handleNotifyUpstream(boolean isCellular) { if (DBG) { mLog.i("notifyUpstream: " + isCellular + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted @@ -244,16 +220,17 @@ public class EntitlementManager { if (mUsingCellularAsUpstream) { final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - handleMaybeRunProvisioning(config); + maybeRunProvisioning(config); } } /** Run provisioning if needed */ public void maybeRunProvisioning() { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + maybeRunProvisioning(config); } - private void handleMaybeRunProvisioning(final TetheringConfiguration config) { + private void maybeRunProvisioning(final TetheringConfiguration config) { if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired(config)) { return; } @@ -319,7 +296,7 @@ public class EntitlementManager { } if (mUsingCellularAsUpstream) { - handleMaybeRunProvisioning(config); + maybeRunProvisioning(config); } } @@ -494,46 +471,6 @@ public class EntitlementManager { } }; - private class EntitlementHandler extends Handler { - EntitlementHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_START_PROVISIONING: - handleStartProvisioningIfNeeded(msg.arg1, toBool(msg.arg2)); - break; - case EVENT_STOP_PROVISIONING: - handleStopProvisioningIfNeeded(msg.arg1); - break; - case EVENT_UPSTREAM_CHANGED: - handleNotifyUpstream(toBool(msg.arg1)); - break; - case EVENT_MAYBE_RUN_PROVISIONING: - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - handleMaybeRunProvisioning(config); - break; - case EVENT_GET_ENTITLEMENT_VALUE: - handleRequestLatestTetheringEntitlementValue(msg.arg1, - (ResultReceiver) msg.obj, toBool(msg.arg2)); - break; - default: - mLog.log("Unknown event: " + msg.what); - break; - } - } - } - - private static boolean toBool(int encodedBoolean) { - return encodedBoolean != 0; - } - - private static int encodeBool(boolean b) { - return b ? 1 : 0; - } - private static boolean isValidDownstreamType(int type) { switch (type) { case TETHERING_BLUETOOTH: @@ -644,13 +581,6 @@ public class EntitlementManager { /** Get the last value of the tethering entitlement check. */ public void requestLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver, boolean showEntitlementUi) { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE, - downstream, encodeBool(showEntitlementUi), receiver)); - - } - - private void handleRequestLatestTetheringEntitlementValue(int downstream, - ResultReceiver receiver, boolean showEntitlementUi) { if (!isValidDownstreamType(downstream)) { receiver.send(TETHER_ERROR_ENTITLEMENT_UNKNOWN, null); return; diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index f3cead92be7e..da8bf54718e9 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -257,7 +257,7 @@ public class Tethering { mContext = mDeps.getContext(); mNetd = mDeps.getINetd(mContext); mLooper = mDeps.getTetheringLooper(); - mNotificationUpdater = mDeps.getNotificationUpdater(mContext); + mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper); mPublicSync = new Object(); @@ -337,6 +337,11 @@ public class Tethering { filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED); mContext.registerReceiver(mStateReceiver, filter, null, mHandler); + final IntentFilter noUpstreamFilter = new IntentFilter(); + noUpstreamFilter.addAction(TetheringNotificationUpdater.ACTION_DISABLE_TETHERING); + mContext.registerReceiver( + mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler); + final WifiManager wifiManager = getWifiManager(); if (wifiManager != null) { wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback()); @@ -855,6 +860,8 @@ public class Tethering { } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) { mLog.log("OBSERVED data saver changed"); handleDataSaverChanged(); + } else if (action.equals(TetheringNotificationUpdater.ACTION_DISABLE_TETHERING)) { + untetherAll(); } } @@ -922,8 +929,10 @@ public class Tethering { case WifiManager.WIFI_AP_STATE_ENABLED: enableWifiIpServingLocked(ifname, ipmode); break; - case WifiManager.WIFI_AP_STATE_DISABLED: case WifiManager.WIFI_AP_STATE_DISABLING: + // We can see this state on the way to disabled. + break; + case WifiManager.WIFI_AP_STATE_DISABLED: case WifiManager.WIFI_AP_STATE_FAILED: default: disableWifiIpServingLocked(ifname, curState); @@ -1944,10 +1953,12 @@ public class Tethering { /** Get the latest value of the tethering entitlement check. */ void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver, boolean showEntitlementUi) { - if (receiver != null) { + if (receiver == null) return; + + mHandler.post(() -> { mEntitlementMgr.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi); - } + }); } /** Register tethering event callback */ @@ -2011,6 +2022,7 @@ public class Tethering { } finally { mTetheringEventCallbacks.finishBroadcast(); } + mNotificationUpdater.onUpstreamNetworkChanged(network); } private void reportConfigurationChanged(TetheringConfigurationParcel config) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index 893c5823dce1..9b54b5ff2403 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -106,8 +106,9 @@ public abstract class TetheringDependencies { /** * Get a reference to the TetheringNotificationUpdater to be used by tethering. */ - public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx) { - return new TetheringNotificationUpdater(ctx); + public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx, + @NonNull final Looper looper) { + return new TetheringNotificationUpdater(ctx, looper); } /** diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java index 42870560cb5e..ff83fd1e4f1e 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java @@ -19,18 +19,25 @@ package com.android.networkstack.tethering; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; +import static android.text.TextUtils.isEmpty; import android.app.Notification; +import android.app.Notification.Action; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.content.res.Resources; +import android.net.Network; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionManager; -import android.text.TextUtils; +import android.telephony.TelephonyManager; import android.util.Log; import android.util.SparseArray; @@ -39,9 +46,13 @@ import androidx.annotation.DrawableRes; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import java.util.Arrays; +import java.util.List; + /** * A class to display tethering-related notifications. * @@ -58,12 +69,22 @@ public class TetheringNotificationUpdater { private static final String WIFI_DOWNSTREAM = "WIFI"; private static final String USB_DOWNSTREAM = "USB"; private static final String BLUETOOTH_DOWNSTREAM = "BT"; + @VisibleForTesting + static final String ACTION_DISABLE_TETHERING = + "com.android.server.connectivity.tethering.DISABLE_TETHERING"; private static final boolean NOTIFY_DONE = true; private static final boolean NO_NOTIFY = false; - // Id to update and cancel tethering notification. Must be unique within the tethering app. - private static final int ENABLE_NOTIFICATION_ID = 1000; + @VisibleForTesting + static final int EVENT_SHOW_NO_UPSTREAM = 1; + // Id to update and cancel enable notification. Must be unique within the tethering app. + @VisibleForTesting + static final int ENABLE_NOTIFICATION_ID = 1000; // Id to update and cancel restricted notification. Must be unique within the tethering app. - private static final int RESTRICTED_NOTIFICATION_ID = 1001; + @VisibleForTesting + static final int RESTRICTED_NOTIFICATION_ID = 1001; + // Id to update and cancel no upstream notification. Must be unique within the tethering app. + @VisibleForTesting + static final int NO_UPSTREAM_NOTIFICATION_ID = 1002; @VisibleForTesting static final int NO_ICON_ID = 0; @VisibleForTesting @@ -71,14 +92,16 @@ public class TetheringNotificationUpdater { private final Context mContext; private final NotificationManager mNotificationManager; private final NotificationChannel mChannel; + private final Handler mHandler; // WARNING : the constructor is called on a different thread. Thread safety therefore - // relies on this value being initialized to 0, and not any other value. If you need + // relies on these values being initialized to 0 or false, and not any other value. If you need // to change this, you will need to change the thread where the constructor is invoked, // or to introduce synchronization. // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2. // This value has to be made 1 2 and 4, and OR'd with the others. private int mDownstreamTypesMask = DOWNSTREAM_NONE; + private boolean mNoUpstream = false; // WARNING : this value is not able to being initialized to 0 and must have volatile because // telephony service is not guaranteed that is up before tethering service starts. If telephony @@ -87,10 +110,30 @@ public class TetheringNotificationUpdater { // INVALID_SUBSCRIPTION_ID. private volatile int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - @IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID}) + @IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID}) @interface NotificationId {} - public TetheringNotificationUpdater(@NonNull final Context context) { + private static final class MccMncOverrideInfo { + public final List<String> visitedMccMncs; + public final int homeMcc; + public final int homeMnc; + MccMncOverrideInfo(List<String> visitedMccMncs, int mcc, int mnc) { + this.visitedMccMncs = visitedMccMncs; + this.homeMcc = mcc; + this.homeMnc = mnc; + } + } + + private static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>(); + + static { + // VZW + sCarrierIdToMccMnc.put( + 1839, new MccMncOverrideInfo(Arrays.asList(new String[] {"20404"}), 311, 480)); + } + + public TetheringNotificationUpdater(@NonNull final Context context, + @NonNull final Looper looper) { mContext = context; mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0) .getSystemService(Context.NOTIFICATION_SERVICE); @@ -99,6 +142,22 @@ public class TetheringNotificationUpdater { context.getResources().getString(R.string.notification_channel_tethering_status), NotificationManager.IMPORTANCE_LOW); mNotificationManager.createNotificationChannel(mChannel); + mHandler = new NotificationHandler(looper); + } + + private class NotificationHandler extends Handler { + NotificationHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case EVENT_SHOW_NO_UPSTREAM: + notifyTetheringNoUpstream(); + break; + } + } } /** Called when downstream has changed */ @@ -106,6 +165,7 @@ public class TetheringNotificationUpdater { if (mDownstreamTypesMask == downstreamTypesMask) return; mDownstreamTypesMask = downstreamTypesMask; updateEnableNotification(); + updateNoUpstreamNotification(); } /** Called when active data subscription id changed */ @@ -113,21 +173,62 @@ public class TetheringNotificationUpdater { if (mActiveDataSubId == subId) return; mActiveDataSubId = subId; updateEnableNotification(); + updateNoUpstreamNotification(); } + /** Called when upstream network changed */ + public void onUpstreamNetworkChanged(@Nullable final Network network) { + final boolean isNoUpstream = (network == null); + if (mNoUpstream == isNoUpstream) return; + mNoUpstream = isNoUpstream; + updateNoUpstreamNotification(); + } + + @NonNull @VisibleForTesting - Resources getResourcesForSubId(@NonNull final Context c, final int subId) { - return SubscriptionManager.getResourcesForSubId(c, subId); + final Handler getHandler() { + return mHandler; + } + + @NonNull + @VisibleForTesting + Resources getResourcesForSubId(@NonNull final Context context, final int subId) { + final Resources res = SubscriptionManager.getResourcesForSubId(context, subId); + final TelephonyManager tm = + ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)) + .createForSubscriptionId(mActiveDataSubId); + final int carrierId = tm.getSimCarrierId(); + final String mccmnc = tm.getSimOperator(); + final MccMncOverrideInfo overrideInfo = sCarrierIdToMccMnc.get(carrierId); + if (overrideInfo != null && overrideInfo.visitedMccMncs.contains(mccmnc)) { + // Re-configure MCC/MNC value to specific carrier to get right resources. + final Configuration config = res.getConfiguration(); + config.mcc = overrideInfo.homeMcc; + config.mnc = overrideInfo.homeMnc; + return context.createConfigurationContext(config).getResources(); + } + return res; } private void updateEnableNotification() { - final boolean tetheringInactive = mDownstreamTypesMask <= DOWNSTREAM_NONE; + final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; if (tetheringInactive || setupNotification() == NO_NOTIFY) { clearNotification(ENABLE_NOTIFICATION_ID); } } + private void updateNoUpstreamNotification() { + final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; + + if (tetheringInactive + || !mNoUpstream + || setupNoUpstreamNotification() == NO_NOTIFY) { + clearNotification(NO_UPSTREAM_NOTIFICATION_ID); + mHandler.removeMessages(EVENT_SHOW_NO_UPSTREAM); + } + } + @VisibleForTesting void tetheringRestrictionLifted() { clearNotification(RESTRICTED_NOTIFICATION_ID); @@ -142,9 +243,38 @@ public class TetheringNotificationUpdater { final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); final String title = res.getString(R.string.disable_tether_notification_title); final String message = res.getString(R.string.disable_tether_notification_message); + if (isEmpty(title) || isEmpty(message)) return; + + final PendingIntent pi = PendingIntent.getActivity( + mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), + 0 /* requestCode */, + new Intent(Settings.ACTION_TETHER_SETTINGS), + Intent.FLAG_ACTIVITY_NEW_TASK, + null /* options */); + + showNotification(R.drawable.stat_sys_tether_general, title, message, + RESTRICTED_NOTIFICATION_ID, pi, new Action[0]); + } + + private void notifyTetheringNoUpstream() { + final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); + final String title = res.getString(R.string.no_upstream_notification_title); + final String message = res.getString(R.string.no_upstream_notification_message); + final String disableButton = + res.getString(R.string.no_upstream_notification_disable_button); + if (isEmpty(title) || isEmpty(message) || isEmpty(disableButton)) return; + + final Intent intent = new Intent(ACTION_DISABLE_TETHERING); + intent.setPackage(mContext.getPackageName()); + final PendingIntent pi = PendingIntent.getBroadcast( + mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), + 0 /* requestCode */, + intent, + 0 /* flags */); + final Action action = new Action.Builder(NO_ICON_ID, disableButton, pi).build(); showNotification(R.drawable.stat_sys_tether_general, title, message, - RESTRICTED_NOTIFICATION_ID); + NO_UPSTREAM_NOTIFICATION_ID, null /* pendingIntent */, action); } /** @@ -179,12 +309,13 @@ public class TetheringNotificationUpdater { * * @return {@link android.util.SparseArray} with downstream types and icon id info. */ + @NonNull @VisibleForTesting SparseArray<Integer> getIcons(@ArrayRes int id, @NonNull Resources res) { final String[] array = res.getStringArray(id); final SparseArray<Integer> icons = new SparseArray<>(); for (String config : array) { - if (TextUtils.isEmpty(config)) continue; + if (isEmpty(config)) continue; final String[] elements = config.split(";"); if (elements.length != 2) { @@ -204,6 +335,18 @@ public class TetheringNotificationUpdater { return icons; } + private boolean setupNoUpstreamNotification() { + final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); + final int delayToShowUpstreamNotification = + res.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul); + + if (delayToShowUpstreamNotification < 0) return NO_NOTIFY; + + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_SHOW_NO_UPSTREAM), + delayToShowUpstreamNotification); + return NOTIFY_DONE; + } + private boolean setupNotification() { final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); final SparseArray<Integer> downstreamIcons = @@ -214,17 +357,22 @@ public class TetheringNotificationUpdater { final String title = res.getString(R.string.tethering_notification_title); final String message = res.getString(R.string.tethering_notification_message); + if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY; - showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID); + final PendingIntent pi = PendingIntent.getActivity( + mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), + 0 /* requestCode */, + new Intent(Settings.ACTION_TETHER_SETTINGS), + Intent.FLAG_ACTIVITY_NEW_TASK, + null /* options */); + + showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID, pi, new Action[0]); return NOTIFY_DONE; } private void showNotification(@DrawableRes final int iconId, @NonNull final String title, - @NonNull final String message, @NotificationId final int id) { - final Intent intent = new Intent(Settings.ACTION_TETHER_SETTINGS); - final PendingIntent pi = PendingIntent.getActivity( - mContext.createContextAsUser(UserHandle.CURRENT, 0), - 0 /* requestCode */, intent, 0 /* flags */, null /* options */); + @NonNull final String message, @NotificationId final int id, @Nullable PendingIntent pi, + @NonNull final Action... actions) { final Notification notification = new Notification.Builder(mContext, mChannel.getId()) .setSmallIcon(iconId) @@ -236,6 +384,7 @@ public class TetheringNotificationUpdater { .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_STATUS) .setContentIntent(pi) + .setActions(actions) .build(); mNotificationManager.notify(null /* tag */, id, notification); diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp index 620261b375d2..6b751afdf58b 100644 --- a/packages/Tethering/tests/integration/Android.bp +++ b/packages/Tethering/tests/integration/Android.bp @@ -13,19 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // - -android_test { - name: "TetheringIntegrationTests", - certificate: "platform", - platform_apis: true, +java_defaults { + name: "TetheringIntegrationTestsDefaults", srcs: [ "src/**/*.java", "src/**/*.kt", ], - test_suites: [ - "device-tests", - "mts", - ], static_libs: [ "NetworkStackApiStableLib", "androidx.test.rules", @@ -44,4 +37,49 @@ android_test { "libdexmakerjvmtiagent", "libstaticjvmtiagent", ], + jarjar_rules: ":NetworkStackJarJarRules", +} + +android_library { + name: "TetheringIntegrationTestsLib", + platform_apis: true, + defaults: ["TetheringIntegrationTestsDefaults"], + visibility: ["//cts/tests/tests/tethering"] +} + +android_test { + name: "TetheringIntegrationTests", + platform_apis: true, + defaults: ["TetheringIntegrationTestsDefaults"], + test_suites: [ + "device-tests", + "mts", + ], + compile_multilib: "both", } + +// Special version of the tethering tests that includes all tests necessary for code coverage +// purposes. This is currently the union of TetheringTests, TetheringIntegrationTests and +// NetworkStackTests. +android_test { + name: "TetheringCoverageTests", + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests", "mts"], + test_config: "AndroidTest_Coverage.xml", + defaults: ["libnetworkstackutilsjni_deps"], + static_libs: [ + "NetworkStackTestsLib", + "TetheringTestsLib", + "TetheringIntegrationTestsLib", + ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + // For NetworkStackUtils included in NetworkStackBase + "libnetworkstackutilsjni", + ], + compile_multilib: "both", + manifest: "AndroidManifest_coverage.xml", +}
\ No newline at end of file diff --git a/packages/Tethering/tests/integration/AndroidManifest.xml b/packages/Tethering/tests/integration/AndroidManifest.xml index 233ba40b5d35..fddfaad29f0f 100644 --- a/packages/Tethering/tests/integration/AndroidManifest.xml +++ b/packages/Tethering/tests/integration/AndroidManifest.xml @@ -17,7 +17,6 @@ package="com.android.networkstack.tethering.tests.integration"> <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/packages/Tethering/tests/integration/AndroidManifest_coverage.xml b/packages/Tethering/tests/integration/AndroidManifest_coverage.xml new file mode 100644 index 000000000000..06de00d78558 --- /dev/null +++ b/packages/Tethering/tests/integration/AndroidManifest_coverage.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.networkstack.tethering.tests.coverage"> + + <application tools:replace="android:label" + android:debuggable="true" + android:label="Tethering coverage tests"> + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.networkstack.tethering.tests.coverage" + android:label="Tethering coverage tests"> + </instrumentation> +</manifest> diff --git a/packages/Tethering/tests/integration/AndroidTest_Coverage.xml b/packages/Tethering/tests/integration/AndroidTest_Coverage.xml new file mode 100644 index 000000000000..3def2099e45f --- /dev/null +++ b/packages/Tethering/tests/integration/AndroidTest_Coverage.xml @@ -0,0 +1,12 @@ +<configuration description="Runs coverage tests for Tethering"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="TetheringCoverageTests.apk" /> + </target_preparer> + + <option name="test-tag" value="TetheringCoverageTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.networkstack.tethering.tests.coverage" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index b02bb23f9807..4bac9da9c3d2 100644 --- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -18,6 +18,7 @@ package android.net; import static android.Manifest.permission.MANAGE_TEST_NETWORKS; import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.net.TetheringManager.TETHERING_ETHERNET; import static org.junit.Assert.assertEquals; @@ -109,7 +110,8 @@ public class EthernetTetheringTest { mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm); // Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive // tethered client callbacks. - mUiAutomation.adoptShellPermissionIdentity(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS); + mUiAutomation.adoptShellPermissionIdentity( + MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED); } private void cleanUp() throws Exception { diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp index 59681e9eb56a..4849fd5d01f5 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -14,39 +14,33 @@ // limitations under the License. // -android_test { - name: "TetheringTests", - certificate: "platform", +java_defaults { + name: "TetheringTestsDefaults", srcs: [ "src/**/*.java", "src/**/*.kt", ], - test_suites: [ - "device-tests", - "mts", - ], - compile_multilib: "both", static_libs: [ + "TetheringApiCurrentLib", "androidx.test.rules", "frameworks-base-testutils", - "net-tests-utils", "mockito-target-extended-minus-junit4", - "TetheringApiCurrentLib", + "net-tests-utils", "testables", ], // TODO(b/147200698) change sdk_version to module-current and // remove framework-minus-apex, ext, and framework-res sdk_version: "core_platform", libs: [ - "framework-minus-apex", - "ext", - "framework-res", - "framework-wifi-stubs-module_libs_api", - "framework-telephony-stubs", "android.test.runner", "android.test.base", "android.test.mock", + "ext", + "framework-minus-apex", + "framework-res", + "framework-telephony-stubs", "framework-tethering", + "framework-wifi-stubs-module_libs_api", ], jni_libs: [ // For mockito extended @@ -55,3 +49,25 @@ android_test { ], jarjar_rules: "jarjar-rules.txt", } + +// Library containing the unit tests. This is used by the coverage test target to pull in the +// unit test code. It is not currently used by the tests themselves because all the build +// configuration needed by the tests is in the TetheringTestsDefaults rule. +android_library { + name: "TetheringTestsLib", + defaults: ["TetheringTestsDefaults"], + visibility: [ + "//frameworks/base/packages/Tethering/tests/integration", + ] +} + +android_test { + name: "TetheringTests", + certificate: "platform", + test_suites: [ + "device-tests", + "mts", + ], + defaults: ["TetheringTestsDefaults"], + compile_multilib: "both", +} diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml index 55640db69324..31eaabff5274 100644 --- a/packages/Tethering/tests/unit/AndroidManifest.xml +++ b/packages/Tethering/tests/unit/AndroidManifest.xml @@ -16,6 +16,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.networkstack.tethering.tests.unit"> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/> <application android:debuggable="true"> diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt index 7bff74b25d94..5f8858857c75 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt @@ -23,14 +23,26 @@ import android.content.res.Resources import android.net.ConnectivityManager.TETHERING_BLUETOOTH import android.net.ConnectivityManager.TETHERING_USB import android.net.ConnectivityManager.TETHERING_WIFI +import android.net.Network +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper import android.os.UserHandle import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID -import androidx.test.platform.app.InstrumentationRegistry +import android.telephony.TelephonyManager import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 import com.android.internal.util.test.BroadcastInterceptingContext import com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE +import com.android.networkstack.tethering.TetheringNotificationUpdater.ENABLE_NOTIFICATION_ID +import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM +import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID +import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID +import com.android.testutils.waitForIdle +import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,8 +55,8 @@ import org.mockito.Mockito.doReturn import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.times -import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations const val TEST_SUBID = 1 @@ -55,10 +67,13 @@ const val GENERAL_ICON_ID = 4 const val WIFI_MASK = 1 shl TETHERING_WIFI const val USB_MASK = 1 shl TETHERING_USB const val BT_MASK = 1 shl TETHERING_BLUETOOTH -const val TITTLE = "Tethering active" +const val TITLE = "Tethering active" const val MESSAGE = "Tap here to set up." -const val TEST_TITTLE = "Hotspot active" +const val TEST_TITLE = "Hotspot active" const val TEST_MESSAGE = "Tap to set up hotspot." +const val TEST_NO_UPSTREAM_TITLE = "Hotspot has no internet access" +const val TEST_NO_UPSTREAM_MESSAGE = "Device cannot connect to internet." +const val TEST_NO_UPSTREAM_BUTTON = "Turn off hotspot" @RunWith(AndroidJUnit4::class) @SmallTest @@ -67,12 +82,15 @@ class TetheringNotificationUpdaterTest { // should crash if they are used before being initialized. @Mock private lateinit var mockContext: Context @Mock private lateinit var notificationManager: NotificationManager + @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var defaultResources: Resources @Mock private lateinit var testResources: Resources - // lateinit for this class under test, as it should be reset to a different instance for every - // tests but should always be initialized before use (or the test should crash). + // lateinit for these classes under test, as they should be reset to a different instance for + // every test but should always be initialized before use (or the test should crash). + private lateinit var context: TestContext private lateinit var notificationUpdater: TetheringNotificationUpdater + private lateinit var fakeTetheringThread: HandlerThread private val ENABLE_ICON_CONFIGS = arrayOf( "USB;android.test:drawable/usb", "BT;android.test:drawable/bluetooth", @@ -82,11 +100,19 @@ class TetheringNotificationUpdaterTest { private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) { override fun createContextAsUser(user: UserHandle, flags: Int) = if (user == UserHandle.ALL) mockContext else this + override fun getSystemService(name: String) = + if (name == Context.TELEPHONY_SERVICE) telephonyManager + else super.getSystemService(name) } - private inner class WrappedNotificationUpdater(c: Context) : TetheringNotificationUpdater(c) { + private inner class WrappedNotificationUpdater(c: Context, looper: Looper) + : TetheringNotificationUpdater(c, looper) { override fun getResourcesForSubId(context: Context, subId: Int) = - if (subId == TEST_SUBID) testResources else defaultResources + when (subId) { + TEST_SUBID -> testResources + INVALID_SUBSCRIPTION_ID -> defaultResources + else -> super.getResourcesForSubId(context, subId) + } } private fun setupResources() { @@ -94,12 +120,20 @@ class TetheringNotificationUpdaterTest { .getStringArray(R.array.tethering_notification_icons) doReturn(arrayOf("WIFI;android.test:drawable/wifi")).`when`(testResources) .getStringArray(R.array.tethering_notification_icons) - doReturn(TITTLE).`when`(defaultResources).getString(R.string.tethering_notification_title) + doReturn(5).`when`(testResources) + .getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul) + doReturn(TITLE).`when`(defaultResources).getString(R.string.tethering_notification_title) doReturn(MESSAGE).`when`(defaultResources) .getString(R.string.tethering_notification_message) - doReturn(TEST_TITTLE).`when`(testResources).getString(R.string.tethering_notification_title) + doReturn(TEST_TITLE).`when`(testResources).getString(R.string.tethering_notification_title) doReturn(TEST_MESSAGE).`when`(testResources) .getString(R.string.tethering_notification_message) + doReturn(TEST_NO_UPSTREAM_TITLE).`when`(testResources) + .getString(R.string.no_upstream_notification_title) + doReturn(TEST_NO_UPSTREAM_MESSAGE).`when`(testResources) + .getString(R.string.no_upstream_notification_message) + doReturn(TEST_NO_UPSTREAM_BUTTON).`when`(testResources) + .getString(R.string.no_upstream_notification_disable_button) doReturn(USB_ICON_ID).`when`(defaultResources) .getIdentifier(eq("android.test:drawable/usb"), any(), any()) doReturn(BT_ICON_ID).`when`(defaultResources) @@ -113,35 +147,61 @@ class TetheringNotificationUpdaterTest { @Before fun setUp() { MockitoAnnotations.initMocks(this) - val context = TestContext(InstrumentationRegistry.getInstrumentation().context) + context = TestContext(InstrumentationRegistry.getInstrumentation().context) doReturn(notificationManager).`when`(mockContext) .getSystemService(Context.NOTIFICATION_SERVICE) - notificationUpdater = WrappedNotificationUpdater(context) + fakeTetheringThread = HandlerThread(this::class.simpleName) + fakeTetheringThread.start() + notificationUpdater = WrappedNotificationUpdater(context, fakeTetheringThread.looper) setupResources() } + @After + fun tearDown() { + fakeTetheringThread.quitSafely() + } + private fun Notification.title() = this.extras.getString(Notification.EXTRA_TITLE) private fun Notification.text() = this.extras.getString(Notification.EXTRA_TEXT) - private fun verifyNotification(iconId: Int = 0, title: String = "", text: String = "") { - verify(notificationManager, never()).cancel(any(), anyInt()) + private fun verifyNotification(iconId: Int, title: String, text: String, id: Int) { + verify(notificationManager, never()).cancel(any(), eq(id)) val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java) verify(notificationManager, times(1)) - .notify(any(), anyInt(), notificationCaptor.capture()) + .notify(any(), eq(id), notificationCaptor.capture()) val notification = notificationCaptor.getValue() assertEquals(iconId, notification.smallIcon.resId) assertEquals(title, notification.title()) assertEquals(text, notification.text()) + } + + private fun verifyNotificationCancelled(id: Int) = + verify(notificationManager, times(1)).cancel(any(), eq(id)) + private val tetheringActiveNotifications = + listOf(NO_UPSTREAM_NOTIFICATION_ID, ENABLE_NOTIFICATION_ID) + + private fun verifyCancelAllTetheringActiveNotifications() { + tetheringActiveNotifications.forEach { + verifyNotificationCancelled(it) + } reset(notificationManager) } - private fun verifyNoNotification() { - verify(notificationManager, times(1)).cancel(any(), anyInt()) - verify(notificationManager, never()).notify(any(), anyInt(), any()) - + private fun verifyOnlyTetheringActiveNotification( + notifyId: Int, + iconId: Int, + title: String, + text: String + ) { + tetheringActiveNotifications.forEach { + when (it) { + notifyId -> verifyNotification(iconId, title, text, notifyId) + else -> verifyNotificationCancelled(it) + } + } reset(notificationManager) } @@ -149,7 +209,7 @@ class TetheringNotificationUpdaterTest { fun testNotificationWithDownstreamChanged() { // Wifi downstream. No notification. notificationUpdater.onDownstreamChanged(WIFI_MASK) - verifyNoNotification() + verifyCancelAllTetheringActiveNotifications() // Same downstream changed. Nothing happened. notificationUpdater.onDownstreamChanged(WIFI_MASK) @@ -157,22 +217,23 @@ class TetheringNotificationUpdaterTest { // Wifi and usb downstreams. Show enable notification notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK) - verifyNotification(GENERAL_ICON_ID, TITTLE, MESSAGE) + verifyOnlyTetheringActiveNotification( + ENABLE_NOTIFICATION_ID, GENERAL_ICON_ID, TITLE, MESSAGE) // Usb downstream. Still show enable notification. notificationUpdater.onDownstreamChanged(USB_MASK) - verifyNotification(USB_ICON_ID, TITTLE, MESSAGE) + verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE) // No downstream. No notification. notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) - verifyNoNotification() + verifyCancelAllTetheringActiveNotifications() } @Test fun testNotificationWithActiveDataSubscriptionIdChanged() { // Usb downstream. Showed enable notification with default resource. notificationUpdater.onDownstreamChanged(USB_MASK) - verifyNotification(USB_ICON_ID, TITTLE, MESSAGE) + verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE) // Same subId changed. Nothing happened. notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID) @@ -180,15 +241,16 @@ class TetheringNotificationUpdaterTest { // Set test sub id. Clear notification with test resource. notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) - verifyNoNotification() + verifyCancelAllTetheringActiveNotifications() // Wifi downstream. Show enable notification with test resource. notificationUpdater.onDownstreamChanged(WIFI_MASK) - verifyNotification(WIFI_ICON_ID, TEST_TITTLE, TEST_MESSAGE) + verifyOnlyTetheringActiveNotification( + ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE) // No downstream. No notification. notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) - verifyNoNotification() + verifyCancelAllTetheringActiveNotifications() } private fun assertIconNumbers(number: Int, configs: Array<String?>) { @@ -227,10 +289,8 @@ class TetheringNotificationUpdaterTest { @Test fun testSetupRestrictedNotification() { - val title = InstrumentationRegistry.getInstrumentation().context.resources - .getString(R.string.disable_tether_notification_title) - val message = InstrumentationRegistry.getInstrumentation().context.resources - .getString(R.string.disable_tether_notification_message) + val title = context.resources.getString(R.string.disable_tether_notification_title) + val message = context.resources.getString(R.string.disable_tether_notification_message) val disallowTitle = "Tether function is disallowed" val disallowMessage = "Please contact your admin" doReturn(title).`when`(defaultResources) @@ -244,18 +304,127 @@ class TetheringNotificationUpdaterTest { // User restrictions on. Show restricted notification. notificationUpdater.notifyTetheringDisabledByRestriction() - verifyNotification(R.drawable.stat_sys_tether_general, title, message) + verifyNotification(R.drawable.stat_sys_tether_general, title, message, + RESTRICTED_NOTIFICATION_ID) + reset(notificationManager) // User restrictions off. Clear notification. notificationUpdater.tetheringRestrictionLifted() - verifyNoNotification() + verifyNotificationCancelled(RESTRICTED_NOTIFICATION_ID) + reset(notificationManager) // Set test sub id. No notification. notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) - verifyNoNotification() + verifyCancelAllTetheringActiveNotifications() // User restrictions on again. Show restricted notification with test resource. notificationUpdater.notifyTetheringDisabledByRestriction() - verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage) + verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage, + RESTRICTED_NOTIFICATION_ID) + reset(notificationManager) + } + + val MAX_BACKOFF_MS = 200L + /** + * Waits for all messages, including delayed ones, to be processed. + * + * This will wait until the handler has no more messages to be processed including + * delayed ones, or the timeout has expired. It uses an exponential backoff strategy + * to wait longer and longer to consume less CPU, with the max granularity being + * MAX_BACKOFF_MS. + * + * @return true if all messages have been processed including delayed ones, false if timeout + * + * TODO: Move this method to com.android.testutils.HandlerUtils.kt. + */ + private fun Handler.waitForDelayedMessage(what: Int?, timeoutMs: Long) { + fun hasMatchingMessages() = + if (what == null) hasMessagesOrCallbacks() else hasMessages(what) + val expiry = System.currentTimeMillis() + timeoutMs + var delay = 5L + while (System.currentTimeMillis() < expiry && hasMatchingMessages()) { + // None of Handler, Looper, Message and MessageQueue expose any way to retrieve + // the time when the next (let alone the last) message will be processed, so + // short of examining the internals with reflection sleep() is the only solution. + Thread.sleep(delay) + delay = (delay * 2) + .coerceAtMost(expiry - System.currentTimeMillis()) + .coerceAtMost(MAX_BACKOFF_MS) + } + + val timeout = expiry - System.currentTimeMillis() + if (timeout <= 0) fail("Delayed message did not process yet after ${timeoutMs}ms") + waitForIdle(timeout) + } + + @Test + fun testNotificationWithUpstreamNetworkChanged() { + // Set test sub id. No notification. + notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) + verifyCancelAllTetheringActiveNotifications() + + // Wifi downstream. Show enable notification with test resource. + notificationUpdater.onDownstreamChanged(WIFI_MASK) + verifyOnlyTetheringActiveNotification( + ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE) + + // There is no upstream. Show no upstream notification. + notificationUpdater.onUpstreamNetworkChanged(null) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L) + verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE, + TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID) + reset(notificationManager) + + // Same upstream network changed. Nothing happened. + notificationUpdater.onUpstreamNetworkChanged(null) + verifyZeroInteractions(notificationManager) + + // Upstream come back. Clear no upstream notification. + notificationUpdater.onUpstreamNetworkChanged(Network(1000)) + verifyNotificationCancelled(NO_UPSTREAM_NOTIFICATION_ID) + reset(notificationManager) + + // No upstream again. Show no upstream notification. + notificationUpdater.onUpstreamNetworkChanged(null) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L) + verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE, + TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID) + reset(notificationManager) + + // No downstream. No notification. + notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) + verifyCancelAllTetheringActiveNotifications() + + // Set R.integer.delay_to_show_no_upstream_after_no_backhaul to 0 and have wifi downstream + // again. Show enable notification only. + doReturn(-1).`when`(testResources) + .getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul) + notificationUpdater.onDownstreamChanged(WIFI_MASK) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L) + verifyOnlyTetheringActiveNotification( + ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE) + } + + @Test + fun testGetResourcesForSubId() { + doReturn(telephonyManager).`when`(telephonyManager).createForSubscriptionId(anyInt()) + doReturn(1234).`when`(telephonyManager).getSimCarrierId() + doReturn("000000").`when`(telephonyManager).getSimOperator() + + val subId = -2 // Use invalid subId to avoid getting resource from cache or real subId. + val config = context.resources.configuration + var res = notificationUpdater.getResourcesForSubId(context, subId) + assertEquals(config.mcc, res.configuration.mcc) + assertEquals(config.mnc, res.configuration.mnc) + + doReturn(1839).`when`(telephonyManager).getSimCarrierId() + res = notificationUpdater.getResourcesForSubId(context, subId) + assertEquals(config.mcc, res.configuration.mcc) + assertEquals(config.mnc, res.configuration.mnc) + + doReturn("20404").`when`(telephonyManager).getSimOperator() + res = notificationUpdater.getResourcesForSubId(context, subId) + assertEquals(311, res.configuration.mcc) + assertEquals(480, res.configuration.mnc) } } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index d4be3a26d958..feb99e6b248d 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -383,7 +383,7 @@ public class TetheringTest { } @Override - public TetheringNotificationUpdater getNotificationUpdater(Context ctx) { + public TetheringNotificationUpdater getNotificationUpdater(Context ctx, Looper looper) { return mNotificationUpdater; } } @@ -1691,6 +1691,18 @@ public class TetheringTest { assertEquals(clientAddrParceled, params.clientAddr); } + @Test + public void testUpstreamNetworkChanged() { + final Tethering.TetherMasterSM stateMachine = (Tethering.TetherMasterSM) + mTetheringDependencies.mUpstreamNetworkMonitorMasterSM; + final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); + when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState); + stateMachine.chooseUpstreamType(true); + + verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(eq(upstreamState.network)); + verify(mNotificationUpdater, times(1)).onUpstreamNetworkChanged(eq(upstreamState.network)); + } + // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 7230b00f87ad..f21f0e73e787 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -197,8 +197,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final MainHandler mMainHandler; - // Lazily initialized - access through getSystemActionPerfomer() - private SystemActionPerformer mSystemActionPerformer; + private final SystemActionPerformer mSystemActionPerformer; private MagnificationController mMagnificationController; @@ -296,6 +295,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManager = mContext.getPackageManager(); mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); + mSystemActionPerformer = + new SystemActionPerformer(mContext, mWindowManagerService, null, this); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); @@ -671,7 +672,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy.enforceCallerIsRecentsOrHasPermission( Manifest.permission.MANAGE_ACCESSIBILITY, FUNCTION_REGISTER_SYSTEM_ACTION); - getSystemActionPerformer().registerSystemAction(actionId, action); + mSystemActionPerformer.registerSystemAction(actionId, action); } /** @@ -684,15 +685,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mSecurityPolicy.enforceCallerIsRecentsOrHasPermission( Manifest.permission.MANAGE_ACCESSIBILITY, FUNCTION_UNREGISTER_SYSTEM_ACTION); - getSystemActionPerformer().unregisterSystemAction(actionId); - } - - private SystemActionPerformer getSystemActionPerformer() { - if (mSystemActionPerformer == null) { - mSystemActionPerformer = - new SystemActionPerformer(mContext, mWindowManagerService, null, this); - } - return mSystemActionPerformer; + mSystemActionPerformer.unregisterSystemAction(actionId); } @Override @@ -804,7 +797,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient, mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, - mSecurityPolicy, this, mWindowManagerService, getSystemActionPerformer(), + mSecurityPolicy, this, mWindowManagerService, mSystemActionPerformer, mA11yWindowManager, flags); onUserStateChangedLocked(getCurrentUserStateLocked()); } @@ -1515,7 +1508,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (service == null) { service = new AccessibilityServiceConnection(userState, mContext, componentName, installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, - this, mWindowManagerService, getSystemActionPerformer(), + this, mWindowManagerService, mSystemActionPerformer, mA11yWindowManager, mActivityTaskManagerService); } else if (userState.mBoundServices.contains(service)) { continue; @@ -2441,7 +2434,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * accessibility button. * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk * version <= Q: turns on / off the accessibility service. - * 3) For services targeting sdk version > Q: + * 3) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk + * version > Q and request accessibility button: turn on the accessibility service if it's + * not in the enabled state. + * (It'll happen when a service is disabled and assigned to shortcut then upgraded.) + * 4) For services targeting sdk version > Q: * a) Turns on / off the accessibility service, if service does not request accessibility * button. * b) Callbacks to accessibility service if service is bounded and requests accessibility @@ -2475,6 +2472,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return true; } + if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY && targetSdk > Build.VERSION_CODES.Q + && requestA11yButton) { + if (!userState.getEnabledServicesLocked().contains(assignedTarget)) { + enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId); + return true; + } + } // Callbacks to a11y service if it's bounded and requests a11y button. if (serviceConnection == null || !userState.mBoundServices.contains(serviceConnection) @@ -2753,7 +2757,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState, mContext, COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, AccessibilityManagerService.this, mWindowManagerService, - getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService) { + mSystemActionPerformer, mA11yWindowManager, mActivityTaskManagerService) { @Override public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { return true; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3d6861898aaf..9d1ad4239a24 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2653,6 +2653,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } else if (viewState.id.equals(this.mCurrentViewId) && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) { requestShowInlineSuggestionsLocked(viewState.getResponse(), filterText); + } else if (viewState.id.equals(this.mCurrentViewId) + && (viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { + if (!TextUtils.isEmpty(filterText)) { + mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); + } } viewState.setState(ViewState.STATE_CHANGED); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e41ba0e1745d..2a9f503602ac 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -465,6 +465,8 @@ public class ActivityManagerService extends IActivityManager.Stub static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; + static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed"; + public static final String ANR_TRACE_DIR = "/data/anr"; // Maximum number of receivers an app can register. @@ -9592,7 +9594,8 @@ public class ActivityManagerService extends IActivityManager.Stub // to handle home activity in this case. if (UserManager.isSplitSystemUser() && Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.USER_SETUP_COMPLETE, 0) != 0) { + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0 + || SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) { t.traceBegin("enableHomeActivity"); ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class); try { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 89fa02bbbd64..cce749d5a7ef 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1668,6 +1668,33 @@ public final class ProcessList { return gidArray; } + private boolean shouldEnableTaggedPointers(ProcessRecord app) { + // Ensure we have platform + kernel support for TBI. + if (!Zygote.nativeSupportsTaggedPointers()) { + return false; + } + + // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute. + if (!app.info.allowsNativeHeapPointerTagging()) { + return false; + } + + // Check to see that the compat feature for TBI is enabled. + if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { + return false; + } + + return true; + } + + private int decideTaggingLevel(ProcessRecord app) { + if (shouldEnableTaggedPointers(app)) { + return Zygote.MEMORY_TAG_LEVEL_TBI; + } + + return 0; + } + private int decideGwpAsanLevel(ProcessRecord app) { // Look at the process attribute first. if (app.processInfo != null @@ -1856,15 +1883,6 @@ public final class ProcessList { runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; } - if (Zygote.nativeSupportsTaggedPointers()) { - // Enable heap pointer tagging if supported by the kernel, unless disabled by the - // app manifest, target sdk level, or compat feature. - if (app.info.allowsNativeHeapPointerTagging() - && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { - runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; - } - } - runtimeFlags |= decideGwpAsanLevel(app); String invokeWith = null; @@ -1895,6 +1913,20 @@ public final class ProcessList { app.setRequiredAbi(requiredAbi); app.instructionSet = instructionSet; + // If instructionSet is non-null, this indicates that the system_server is spawning a + // process with an ISA that may be different from its own. System (kernel and hardware) + // compatililty for these features is checked in the decideTaggingLevel in the + // system_server process (not the child process). As TBI is only supported in aarch64, + // we can simply ensure that the new process is also aarch64. This prevents the mismatch + // where a 64-bit system server spawns a 32-bit child that thinks it should enable some + // tagging variant. Theoretically, a 32-bit system server could exist that spawns 64-bit + // processes, in which case the new process won't get any tagging. This is fine as we + // haven't seen this configuration in practice, and we can reasonable assume that if + // tagging is desired, the system server will be 64-bit. + if (instructionSet == null || instructionSet.equals("arm64")) { + runtimeFlags |= decideTaggingLevel(app); + } + // the per-user SELinux context must be set if (TextUtils.isEmpty(app.info.seInfoUser)) { Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined", diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index e02c6f9d5497..546025a2498f 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -40,6 +40,7 @@ import static com.android.server.am.UserState.STATE_RUNNING_LOCKED; import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED; import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -89,6 +90,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.proto.ProtoOutputStream; import com.android.internal.R; @@ -112,6 +114,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; /** @@ -162,6 +165,46 @@ class UserController implements Handler.Callback { // TODO(b/149604218): STOPSHIP remove this constant and the logcat private static final boolean TESTS_NEED_LOGCAT = true; + // Used for statsd logging with UserLifecycleJourneyReported + UserLifecycleEventOccurred atoms + private static final long INVALID_SESSION_ID = 0; + + // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd + private static final int USER_JOURNEY_UNKNOWN = + FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN; + private static final int USER_JOURNEY_USER_SWITCH_FG = + FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG; + private static final int USER_JOURNEY_USER_SWITCH_UI = + FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI; + private static final int USER_JOURNEY_USER_START = + FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START; + private static final int USER_JOURNEY_USER_CREATE = + FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE; + @IntDef(prefix = { "USER_JOURNEY" }, value = { + USER_JOURNEY_UNKNOWN, + USER_JOURNEY_USER_SWITCH_FG, + USER_JOURNEY_USER_SWITCH_UI, + USER_JOURNEY_USER_START, + USER_JOURNEY_USER_CREATE, + }) + @interface UserJourney {} + + // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd + private static final int USER_LIFECYCLE_EVENT_UNKNOWN = + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN; + private static final int USER_LIFECYCLE_EVENT_SWITCH_USER = + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER; + private static final int USER_LIFECYCLE_EVENT_START_USER = + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER; + private static final int USER_LIFECYCLE_EVENT_CREATE_USER = + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER; + @IntDef(prefix = { "USER_LIFECYCLE_EVENT" }, value = { + USER_LIFECYCLE_EVENT_UNKNOWN, + USER_LIFECYCLE_EVENT_SWITCH_USER, + USER_LIFECYCLE_EVENT_START_USER, + USER_LIFECYCLE_EVENT_CREATE_USER, + }) + @interface UserLifecycleEvent {} + /** * Maximum number of users we allow to be running at a time, including system user. * @@ -270,6 +313,13 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>(); + /** + * A per-user, journey to session id map, used for statsd logging for the + * UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms. + */ + @GuardedBy("mUserJourneyToSessionIdMap") + private final SparseArray<SparseLongArray> mUserJourneyToSessionIdMap = new SparseArray<>(); + UserController(ActivityManagerService service) { this(new Injector(service)); } @@ -2349,6 +2399,10 @@ class UserController implements Handler.Callback { public boolean handleMessage(Message msg) { switch (msg.what) { case START_USER_SWITCH_FG_MSG: + logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1), + USER_JOURNEY_USER_SWITCH_FG); + logUserLifecycleEvent(msg.arg1, USER_JOURNEY_USER_SWITCH_FG, + USER_LIFECYCLE_EVENT_SWITCH_USER, true); startUserInForeground(msg.arg1); break; case REPORT_USER_SWITCH_MSG: @@ -2370,8 +2424,14 @@ class UserController implements Handler.Callback { mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_RUNNING_START, Integer.toString(msg.arg1), msg.arg1); + logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START); + logUserLifecycleEvent(msg.arg1, USER_JOURNEY_USER_START, + USER_LIFECYCLE_EVENT_START_USER, true); mInjector.getSystemServiceManager().startUser(TimingsTraceAndSlog.newAsyncLog(), msg.arg1); + logUserLifecycleEvent(msg.arg1, USER_JOURNEY_USER_START, + USER_LIFECYCLE_EVENT_START_USER, false); + clearSessionId(msg.arg1, USER_JOURNEY_USER_START); break; case USER_UNLOCK_MSG: final int userId = msg.arg1; @@ -2400,17 +2460,94 @@ class UserController implements Handler.Callback { break; case REPORT_USER_SWITCH_COMPLETE_MSG: dispatchUserSwitchComplete(msg.arg1); + final int currentJourney = mUserSwitchUiEnabled ? USER_JOURNEY_USER_SWITCH_UI + : USER_JOURNEY_USER_SWITCH_FG; + logUserLifecycleEvent(msg.arg1, currentJourney, + USER_LIFECYCLE_EVENT_SWITCH_USER, false); + clearSessionId(msg.arg1, currentJourney); break; case REPORT_LOCKED_BOOT_COMPLETE_MSG: dispatchLockedBootComplete(msg.arg1); break; case START_USER_SWITCH_UI_MSG: - showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj); + final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj; + logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second, + USER_JOURNEY_USER_SWITCH_UI); + logUserLifecycleEvent(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI, + USER_LIFECYCLE_EVENT_SWITCH_USER, true); + showUserSwitchDialog(fromToUserPair); break; } return false; } + /** + * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred + * atom given the originating and targeting users for the journey. + * + * Note: these info atoms are currently logged more than once per journey since there is no + * state associated with the user's ongoing journey - this will be updated in a later CL. + */ + private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) { + final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE); + synchronized (mUserJourneyToSessionIdMap) { + SparseLongArray userSessions = mUserJourneyToSessionIdMap.get(target.id); + if (userSessions == null) { + userSessions = new SparseLongArray(); + mUserJourneyToSessionIdMap.put(target.id, userSessions); + } + final long oldSessionId = userSessions.get(journey); + if (oldSessionId != INVALID_SESSION_ID) { + // potentially an incomplete or timed-out session + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, + oldSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN, + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE); + } + // update session id + userSessions.put(journey, newSessionId); + } + + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId, + journey, origin != null ? origin.id : -1, + target.id, UserManager.getUserTypeForStatsd(target.userType), target.flags); + } + + /** + * statsd helper method for logging the begin or finish of the given event for the + * UserLifecycleEventOccurred statsd atom. + * Note: This does not clear the user's journey session id - if this event represents the end of + * a particular journey, call {@link #clearSessionId} to indicate that the session is over. + */ + private void logUserLifecycleEvent(@UserIdInt int userId, @UserJourney int journey, + @UserLifecycleEvent int event, boolean begin) { + final long sessionId; + synchronized (mUserJourneyToSessionIdMap) { + final SparseLongArray eventToSessionMap = mUserJourneyToSessionIdMap.get(userId); + if (eventToSessionMap == null || eventToSessionMap.size() == 0) { + return; + } + sessionId = eventToSessionMap.get(journey); + if (sessionId == INVALID_SESSION_ID) { + return; + } + } + + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId, + event, begin ? FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN + : FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH); + } + + /** + * Clears the user's session id associated with the given UserJourney (for statsd). + */ + private void clearSessionId(@UserIdInt int userId, @UserJourney int journey) { + synchronized (mUserJourneyToSessionIdMap) { + if (mUserJourneyToSessionIdMap.get(userId) != null) { + mUserJourneyToSessionIdMap.get(userId).delete(journey); + } + } + } + private static class UserProgressListener extends IProgressListener.Stub { private volatile long mUnlockStarted; @Override diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 31bcceaba889..8ecda8f1a131 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2830,7 +2830,6 @@ public class AppOpsService extends IAppOpsService.Stub { private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { - verifyIncomingUid(uid); verifyIncomingOp(code); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index 9c03a3606e6c..604b9f1ead6d 100644 --- a/services/core/java/com/android/server/appop/TEST_MAPPING +++ b/services/core/java/com/android/server/appop/TEST_MAPPING @@ -38,6 +38,9 @@ }, { "name": "CtsAppTestCases:ActivityManagerApi29Test" + }, + { + "name": "UidAtomTests:testAppOps" } ] } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 387a2be41d3c..f840f2d359d5 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -185,6 +185,9 @@ public class AudioService extends IAudioService.Stub private static final String TAG = "AS.AudioService"; + private final AudioSystemAdapter mAudioSystem; + private final SystemServerAdapter mSystemServer; + /** Debug audio mode */ protected static final boolean DEBUG_MODE = false; @@ -649,10 +652,19 @@ public class AudioService extends IAudioService.Stub /** @hide */ public AudioService(Context context) { + this(context, AudioSystemAdapter.getDefaultAdapter(), + SystemServerAdapter.getDefaultAdapter(context)); + } + + public AudioService(Context context, AudioSystemAdapter audioSystem, + SystemServerAdapter systemServer) { mContext = context; mContentResolver = context.getContentResolver(); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + mAudioSystem = audioSystem; + mSystemServer = systemServer; + mPlatformType = AudioSystem.getPlatformType(context); mIsSingleVolume = AudioSystem.isSingleVolume(context); @@ -842,11 +854,13 @@ public class AudioService extends IAudioService.Stub context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); - LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); + if (mSystemServer.isPrivileged()) { + LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); - mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); + mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); - mRecordMonitor.initMonitor(); + mRecordMonitor.initMonitor(); + } final float[] preScale = new float[3]; preScale[0] = mContext.getResources().getFraction( @@ -935,7 +949,7 @@ public class AudioService extends IAudioService.Stub onIndicateSystemReady(); - mMicMuteFromSystemCached = AudioSystem.isMicrophoneMuted(); + mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted(); setMicMuteFromSwitchInput(); } @@ -1636,12 +1650,15 @@ public class AudioService extends IAudioService.Stub } if (currentImeUid != mCurrentImeUid || forceUpdate) { - AudioSystem.setCurrentImeUid(currentImeUid); + mAudioSystem.setCurrentImeUid(currentImeUid); mCurrentImeUid = currentImeUid; } } private void readPersistedSettings() { + if (!mSystemServer.isPrivileged()) { + return; + } final ContentResolver cr = mContentResolver; int ringerModeFromSettings = @@ -1712,6 +1729,9 @@ public class AudioService extends IAudioService.Stub } private void readUserRestrictions() { + if (!mSystemServer.isPrivileged()) { + return; + } final int currentUser = getCurrentUserId(); // Check the current user restriction. @@ -2782,6 +2802,9 @@ public class AudioService extends IAudioService.Stub } private void sendBroadcastToAll(Intent intent) { + if (!mSystemServer.isPrivileged()) { + return; + } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long ident = Binder.clearCallingIdentity(); @@ -3174,12 +3197,12 @@ public class AudioService extends IAudioService.Stub } // only mute for the current user if (getCurrentUserId() == userId || userId == android.os.Process.SYSTEM_UID) { - final boolean currentMute = AudioSystem.isMicrophoneMuted(); + final boolean currentMute = mAudioSystem.isMicrophoneMuted(); final long identity = Binder.clearCallingIdentity(); - final int ret = AudioSystem.muteMicrophone(muted); + final int ret = mAudioSystem.muteMicrophone(muted); // update cache with the real state independently from what was set - mMicMuteFromSystemCached = AudioSystem.isMicrophoneMuted(); + mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted(); if (ret != AudioSystem.AUDIO_STATUS_OK) { Log.e(TAG, "Error changing mic mute state to " + muted + " current:" + mMicMuteFromSystemCached); @@ -4518,6 +4541,9 @@ public class AudioService extends IAudioService.Stub } private void broadcastRingerMode(String action, int ringerMode) { + if (!mSystemServer.isPrivileged()) { + return; + } // Send sticky broadcast Intent broadcast = new Intent(action); broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode); @@ -4527,6 +4553,9 @@ public class AudioService extends IAudioService.Stub } private void broadcastVibrateSetting(int vibrateType) { + if (!mSystemServer.isPrivileged()) { + return; + } // Send broadcast if (mActivityManagerInternal.isSystemReady()) { Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); @@ -5258,6 +5287,9 @@ public class AudioService extends IAudioService.Stub } public int observeDevicesForStream_syncVSS(boolean checkOthers) { + if (!mSystemServer.isPrivileged()) { + return AudioSystem.DEVICE_NONE; + } final int devices = AudioSystem.getDevicesForStream(mStreamType); if (devices == mObservedDevices) { return devices; @@ -5998,10 +6030,7 @@ public class AudioService extends IAudioService.Stub break; case MSG_BROADCAST_MICROPHONE_MUTE: - mContext.sendBroadcastAsUser( - new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - UserHandle.ALL); + mSystemServer.sendMicrophoneMuteChangedIntent(); break; } } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 9f8f9f8fddde..40c13904fbc9 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -40,10 +40,11 @@ public class AudioSystemAdapter { /** * Create an adapter for AudioSystem that always succeeds, and does nothing. - * @return a no-op AudioSystem adapter + * Overridden methods can be configured + * @return a no-op AudioSystem adapter with configurable adapter */ - static final @NonNull AudioSystemAdapter getAlwaysOkAdapter() { - return new AudioSystemOkAdapter(); + static final @NonNull AudioSystemAdapter getConfigurableAdapter() { + return new AudioSystemConfigurableAdapter(); } /** @@ -113,10 +114,51 @@ public class AudioSystemAdapter { return AudioSystem.setParameters(keyValuePairs); } + /** + * Same as {@link AudioSystem#isMicrophoneMuted()}} + * Checks whether the microphone mute is on or off. + * @return true if microphone is muted, false if it's not + */ + public boolean isMicrophoneMuted() { + return AudioSystem.isMicrophoneMuted(); + } + + /** + * Same as {@link AudioSystem#muteMicrophone(boolean)} + * Sets the microphone mute on or off. + * + * @param on set <var>true</var> to mute the microphone; + * <var>false</var> to turn mute off + * @return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR + */ + public int muteMicrophone(boolean on) { + return AudioSystem.muteMicrophone(on); + } + + /** + * Same as {@link AudioSystem#setCurrentImeUid(int)} + * Communicate UID of current InputMethodService to audio policy service. + */ + public int setCurrentImeUid(int uid) { + return AudioSystem.setCurrentImeUid(uid); + } + //-------------------------------------------------------------------- - protected static class AudioSystemOkAdapter extends AudioSystemAdapter { + protected static class AudioSystemConfigurableAdapter extends AudioSystemAdapter { private static final String TAG = "ASA"; + private boolean mIsMicMuted = false; + private boolean mMuteMicrophoneFails = false; + + public void configureIsMicrophoneMuted(boolean muted) { + mIsMicMuted = muted; + } + public void configureMuteMicrophoneToFail(boolean fail) { + mMuteMicrophoneFails = fail; + } + + //----------------------------------------------------------------- + // Overrides of AudioSystemAdapter @Override public int setDeviceConnectionState(int device, int state, String deviceAddress, String deviceName, int codecFormat) { @@ -152,5 +194,24 @@ public class AudioSystemAdapter { public int setParameters(String keyValuePairs) { return AudioSystem.AUDIO_STATUS_OK; } + + @Override + public boolean isMicrophoneMuted() { + return mIsMicMuted; + } + + @Override + public int muteMicrophone(boolean on) { + if (mMuteMicrophoneFails) { + return AudioSystem.AUDIO_STATUS_ERROR; + } + mIsMicMuted = on; + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override + public int setCurrentImeUid(int uid) { + return AudioSystem.AUDIO_STATUS_OK; + } } } diff --git a/services/core/java/com/android/server/audio/SystemServerAdapter.java b/services/core/java/com/android/server/audio/SystemServerAdapter.java new file mode 100644 index 000000000000..509f6be76f17 --- /dev/null +++ b/services/core/java/com/android/server/audio/SystemServerAdapter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.os.UserHandle; + +/** + * Provides an adapter to access functionality reserved to components running in system_server + * Functionality such as sending privileged broadcasts is to be accessed through the default + * adapter, whereas tests can inject a no-op adapter. + */ +public class SystemServerAdapter { + + protected final Context mContext; + + private SystemServerAdapter(@Nullable Context context) { + mContext = context; + } + /** + * Create a wrapper around privileged functionality. + * @return the adapter + */ + static final @NonNull SystemServerAdapter getDefaultAdapter(Context context) { + return new SystemServerAdapter(context); + } + + /** + * Create an adapter that does nothing. + * Use for running non-privileged tests, such as unit tests + * @return a no-op adapter + */ + static final @NonNull SystemServerAdapter getNoOpAdapter() { + return new NoOpSystemServerAdapter(); + } + + /** + * @return true if this is supposed to be run in system_server, false otherwise (e.g. for a + * unit test) + */ + public boolean isPrivileged() { + return true; + } + + /** + * Broadcast ACTION_MICROPHONE_MUTE_CHANGED + */ + public void sendMicrophoneMuteChangedIntent() { + mContext.sendBroadcastAsUser( + new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), + UserHandle.ALL); + } + + //-------------------------------------------------------------------- + protected static class NoOpSystemServerAdapter extends SystemServerAdapter { + + NoOpSystemServerAdapter() { + super(null); + } + + @Override + public boolean isPrivileged() { + return false; + } + + @Override + public void sendMicrophoneMuteChangedIntent() { + // no-op + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index a87fb8b5c301..b4f7cdbd5694 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -552,7 +552,8 @@ public final class DisplayManagerService extends SystemService { } if (state == Display.STATE_OFF) { brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT; - } else if (brightnessState < PowerManager.BRIGHTNESS_MIN || Float.isNaN(brightnessState)) { + } else if (brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT + && brightnessState < PowerManager.BRIGHTNESS_MIN) { brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; } else if (brightnessState > PowerManager.BRIGHTNESS_MAX) { brightnessState = PowerManager.BRIGHTNESS_MAX; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 4687a5117343..48e30bf42c2d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -390,6 +390,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private ObjectAnimator mColorFadeOffAnimator; private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; + // The brightness synchronizer to allow changes in the int brightness value to be reflected in + // the float brightness value and vice versa. + @Nullable + private final BrightnessSynchronizer mBrightnessSynchronizer; /** * Creates the display power controller. @@ -406,6 +410,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); mBlanker = blanker; mContext = context; + mBrightnessSynchronizer = new BrightnessSynchronizer(context); mDisplayDevice = displayDevice; PowerManager pm = context.getSystemService(PowerManager.class); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e6cb37185d71..b949d6bcf2e2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -784,6 +784,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); private static final class Entry { + final int mSequenceNumber = sSequenceNumber.getAndIncrement(); final ClientState mClientState; @SoftInputModeFlags final int mFocusedWindowSoftInputMode; @@ -831,7 +832,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub continue; } pw.print(prefix); - pw.println("SoftInputShowHideHistory #" + sSequenceNumber.getAndIncrement() + ":"); + pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":"); pw.print(prefix); pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime)) diff --git a/services/core/java/com/android/server/location/AppOpsHelper.java b/services/core/java/com/android/server/location/AppOpsHelper.java index cb64c50bf11d..c598fb1dbe26 100644 --- a/services/core/java/com/android/server/location/AppOpsHelper.java +++ b/services/core/java/com/android/server/location/AppOpsHelper.java @@ -19,8 +19,8 @@ package com.android.server.location; import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_LOCATION; -import static com.android.server.LocationManagerService.D; -import static com.android.server.LocationManagerService.TAG; +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; import android.annotation.Nullable; import android.app.AppOpsManager; diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java index 195b059b7374..095cd146da4f 100644 --- a/services/core/java/com/android/server/location/GeofenceManager.java +++ b/services/core/java/com/android/server/location/GeofenceManager.java @@ -253,7 +253,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish int op = CallerIdentity.asAppOp(identity.permissionLevel); if (op >= 0) { if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, identity.uid, - identity.packageName, identity.featureId, null) + identity.packageName, identity.featureId, identity.listenerId) != AppOpsManager.MODE_ALLOWED) { continue; } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 7f25de6b3470..4f8708a7599a 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.location; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -36,6 +36,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; @@ -91,27 +92,14 @@ import com.android.internal.location.ProviderRequest; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; -import com.android.server.location.AbstractLocationProvider; +import com.android.server.FgThread; +import com.android.server.LocalServices; +import com.android.server.PendingIntentUtils; +import com.android.server.SystemService; import com.android.server.location.AbstractLocationProvider.State; -import com.android.server.location.AppForegroundHelper; -import com.android.server.location.AppOpsHelper; -import com.android.server.location.CallerIdentity; import com.android.server.location.CallerIdentity.PermissionLevel; -import com.android.server.location.GeocoderProxy; -import com.android.server.location.GeofenceManager; -import com.android.server.location.GeofenceProxy; -import com.android.server.location.HardwareActivityRecognitionProxy; -import com.android.server.location.LocationFudger; -import com.android.server.location.LocationProviderProxy; -import com.android.server.location.LocationRequestStatistics; import com.android.server.location.LocationRequestStatistics.PackageProviderKey; import com.android.server.location.LocationRequestStatistics.PackageStatistics; -import com.android.server.location.LocationUsageLogger; -import com.android.server.location.MockProvider; -import com.android.server.location.MockableLocationProvider; -import com.android.server.location.PassiveProvider; -import com.android.server.location.SettingsHelper; -import com.android.server.location.UserInfoHelper; import com.android.server.location.UserInfoHelper.UserListener; import com.android.server.location.gnss.GnssManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -1620,8 +1608,8 @@ public class LocationManagerService extends ILocationManager.Stub { // For now, make sure callers have supplied an attribution tag for use with // AppOpsManager. This might be relaxed in the future. final List<WorkChain> workChains = workSource.getWorkChains(); - return workChains != null && !workChains.isEmpty() && - workChains.get(0).getAttributionTag() != null; + return workChains != null && !workChains.isEmpty() + && workChains.get(0).getAttributionTag() != null; } } @@ -1840,6 +1828,9 @@ public class LocationManagerService extends ILocationManager.Stub { if (request == null) { request = DEFAULT_LOCATION_REQUEST; } + if (listenerId == null && intent != null) { + listenerId = AppOpsManager.toReceiverId(intent); + } CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId, listenerId); @@ -2106,7 +2097,8 @@ public class LocationManagerService extends ILocationManager.Stub { request = DEFAULT_LOCATION_REQUEST; } - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId); + CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId, + AppOpsManager.toReceiverId(intent)); identity.enforceLocationPermission(); Objects.requireNonNull(intent); diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/location/LocationManagerServiceUtils.java index 9d0fe5e936bb..c33a70662cb5 100644 --- a/services/core/java/com/android/server/LocationManagerServiceUtils.java +++ b/services/core/java/com/android/server/location/LocationManagerServiceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,13 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.location; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; import android.os.RemoteException; -import com.android.server.location.CallerIdentity; - import java.util.NoSuchElementException; import java.util.function.Consumer; diff --git a/services/core/java/com/android/server/location/LocationUsageLogger.java b/services/core/java/com/android/server/location/LocationUsageLogger.java index 93e19df01cf3..b325deb786d6 100644 --- a/services/core/java/com/android/server/location/LocationUsageLogger.java +++ b/services/core/java/com/android/server/location/LocationUsageLogger.java @@ -16,7 +16,7 @@ package com.android.server.location; -import static com.android.server.LocationManagerService.TAG; +import static com.android.server.location.LocationManagerService.TAG; import android.app.ActivityManager; import android.location.Geofence; diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java index 7ab258c29b46..cbb06b86a291 100644 --- a/services/core/java/com/android/server/location/SettingsHelper.java +++ b/services/core/java/com/android/server/location/SettingsHelper.java @@ -26,8 +26,8 @@ import static android.provider.Settings.Secure.LOCATION_COARSE_ACCURACY_M; import static android.provider.Settings.Secure.LOCATION_MODE; import static android.provider.Settings.Secure.LOCATION_MODE_OFF; -import static com.android.server.LocationManagerService.D; -import static com.android.server.LocationManagerService.TAG; +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; import android.app.ActivityManager; import android.content.Context; diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java index 28f3f476847b..a3dcc40bdf2d 100644 --- a/services/core/java/com/android/server/location/UserInfoHelper.java +++ b/services/core/java/com/android/server/location/UserInfoHelper.java @@ -18,8 +18,8 @@ package com.android.server.location; import static android.os.UserManager.DISALLOW_SHARE_LOCATION; -import static com.android.server.LocationManagerService.D; -import static com.android.server.LocationManagerService.TAG; +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; import android.annotation.IntDef; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 711f45cb7d28..3c509c380374 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -46,11 +46,11 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; -import com.android.server.LocationManagerServiceUtils.LinkedListener; -import com.android.server.LocationManagerServiceUtils.LinkedListenerBase; import com.android.server.location.AppForegroundHelper; import com.android.server.location.AppOpsHelper; import com.android.server.location.CallerIdentity; +import com.android.server.location.LocationManagerServiceUtils.LinkedListener; +import com.android.server.location.LocationManagerServiceUtils.LinkedListenerBase; import com.android.server.location.LocationUsageLogger; import com.android.server.location.RemoteListenerHelper; import com.android.server.location.SettingsHelper; diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 9297a43b04aa..7972f247b46d 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -366,10 +366,15 @@ public class LockSettingsService extends ILockSettings.Stub { if (mStorage.hasChildProfileLock(managedUserId)) { return; } - // Do not tie it to parent when parent does not have a screen lock + // If parent does not have a screen lock, simply clear credential from the managed profile, + // to maintain the invariant that unified profile should always have the same secure state + // as its parent. final int parentId = mUserManager.getProfileParent(managedUserId).id; - if (!isUserSecure(parentId)) { - if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock"); + if (!isUserSecure(parentId) && !managedUserPassword.isNone()) { + if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock but profile has one"); + + setLockCredentialInternal(LockscreenCredential.createNone(), managedUserPassword, + managedUserId, /* isLockTiedToParent= */ true); return; } // Do not tie when the parent has no SID (but does have a screen lock). @@ -3161,6 +3166,21 @@ public class LockSettingsService extends ILockSettings.Stub { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(timestamp)); } + private static String credentialTypeToString(int credentialType) { + switch (credentialType) { + case CREDENTIAL_TYPE_NONE: + return "None"; + case CREDENTIAL_TYPE_PATTERN: + return "Pattern"; + case CREDENTIAL_TYPE_PIN: + return "Pin"; + case CREDENTIAL_TYPE_PASSWORD: + return "Password"; + default: + return "Unknown " + credentialType; + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, printWriter)) return; @@ -3192,7 +3212,8 @@ public class LockSettingsService extends ILockSettings.Stub { // It's OK to dump the password type since anyone with physical access can just // observe it from the keyguard directly. pw.println("Quality: " + getKeyguardStoredQuality(userId)); - pw.println("CredentialType: " + getCredentialTypeInternal(userId)); + pw.println("CredentialType: " + credentialTypeToString( + getCredentialTypeInternal(userId))); pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabledInternal(userId)); pw.println(String.format("Metrics: %s", getUserPasswordMetrics(userId) != null ? "known" : "unknown")); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6e2feeb15e21..1345e3759d2f 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -261,8 +261,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { .build(); builder.addSelectedRoute(mSelectedRouteId); - for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) { - builder.addTransferableRoute(route.getId()); + if (mBtRouteProvider != null) { + for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) { + builder.addTransferableRoute(route.getId()); + } } RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build(); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index d8264b36256d..f7d0d4ee16eb 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -46,7 +46,6 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; -import static android.net.NetworkTemplate.getCollapsedRatType; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; import static android.os.Trace.TRACE_TAG_NETWORK; @@ -67,9 +66,6 @@ import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION; import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE; import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES; import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE; -import static android.telephony.PhoneStateListener.LISTEN_NONE; -import static android.telephony.PhoneStateListener.LISTEN_SERVICE_STATE; -import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; @@ -133,9 +129,7 @@ import android.provider.Settings.Global; import android.service.NetworkInterfaceProto; import android.service.NetworkStatsServiceDumpProto; import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; import android.telephony.SubscriptionPlan; -import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -206,7 +200,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final NetworkStatsFactory mStatsFactory; private final AlarmManager mAlarmManager; private final Clock mClock; - private final TelephonyManager mTeleManager; private final NetworkStatsSettings mSettings; private final NetworkStatsObservers mStatsObservers; @@ -352,6 +345,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull private final Dependencies mDeps; + @NonNull + private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; + private static @NonNull File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -401,8 +397,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager, - wakeLock, getDefaultClock(), context.getSystemService(TelephonyManager.class), + final NetworkStatsService service = new NetworkStatsService(context, networkManager, + alarmManager, wakeLock, getDefaultClock(), new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(), new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(), new Dependencies()); @@ -416,16 +412,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @VisibleForTesting NetworkStatsService(Context context, INetworkManagementService networkManager, AlarmManager alarmManager, PowerManager.WakeLock wakeLock, Clock clock, - TelephonyManager teleManager, NetworkStatsSettings settings, - NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir, - File baseDir, @NonNull Dependencies deps) { + NetworkStatsSettings settings, NetworkStatsFactory factory, + NetworkStatsObservers statsObservers, File systemDir, File baseDir, + @NonNull Dependencies deps) { mContext = Objects.requireNonNull(context, "missing Context"); mNetworkManager = Objects.requireNonNull(networkManager, - "missing INetworkManagementService"); + "missing INetworkManagementService"); mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager"); mClock = Objects.requireNonNull(clock, "missing Clock"); mSettings = Objects.requireNonNull(settings, "missing NetworkStatsSettings"); - mTeleManager = Objects.requireNonNull(teleManager, "missing TelephonyManager"); mWakeLock = Objects.requireNonNull(wakeLock, "missing WakeLock"); mStatsFactory = Objects.requireNonNull(factory, "missing factory"); mStatsObservers = Objects.requireNonNull(statsObservers, "missing NetworkStatsObservers"); @@ -437,7 +432,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final HandlerThread handlerThread = mDeps.makeHandlerThread(); handlerThread.start(); mHandler = new NetworkStatsHandler(handlerThread.getLooper()); - mPhoneListener = new NetworkTypeListener(new HandlerExecutor(mHandler)); + mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext, + new HandlerExecutor(mHandler), this); } /** @@ -453,6 +449,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public HandlerThread makeHandlerThread() { return new HandlerThread(TAG); } + + /** + * Create a {@link NetworkStatsSubscriptionsMonitor}, can be used to monitor RAT change + * event in NetworkStatsService. + */ + @NonNull + public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor(@NonNull Context context, + @NonNull Executor executor, @NonNull NetworkStatsService service) { + // TODO: Update RatType passively in NSS, instead of querying into the monitor + // when forceUpdateIface. + return new NetworkStatsSubscriptionsMonitor(context, executor, (subscriberId, type) -> + service.handleOnCollapsedRatTypeChanged()); + } } private void registerLocalService() { @@ -517,11 +526,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime, mSettings.getPollInterval(), pollIntent); - // TODO: 1. listen to changes from all subscriptions. - // 2. listen to settings changed to support dynamically enable/disable. + // TODO: listen to settings changed to support dynamically enable/disable. // watch for networkType changes if (!mSettings.getCombineSubtypeEnabled()) { - mTeleManager.listen(mPhoneListener, LISTEN_SERVICE_STATE); + mNetworkStatsSubscriptionsMonitor.start(); } registerGlobalAlert(); @@ -544,7 +552,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.unregisterReceiver(mUserReceiver); mContext.unregisterReceiver(mShutdownReceiver); - mTeleManager.listen(mPhoneListener, LISTEN_NONE); + if (!mSettings.getCombineSubtypeEnabled()) { + mNetworkStatsSubscriptionsMonitor.stop(); + } final long currentTime = mClock.millis(); @@ -1197,35 +1207,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { }; /** - * Receiver that watches for {@link TelephonyManager} changes, such as - * transitioning between Radio Access Technology(RAT) types. + * Handle collapsed RAT type changed event. */ - @NonNull - private final NetworkTypeListener mPhoneListener; - - class NetworkTypeListener extends PhoneStateListener { - private volatile int mLastCollapsedRatType = NETWORK_TYPE_UNKNOWN; - - NetworkTypeListener(@NonNull Executor executor) { - super(executor); - } - - @Override - public void onServiceStateChanged(@NonNull ServiceState ss) { - final int networkType = ss.getDataNetworkType(); - final int collapsedRatType = getCollapsedRatType(networkType); - if (collapsedRatType == mLastCollapsedRatType) return; - - if (LOGD) { - Log.d(TAG, "subtype changed for mobile: " - + mLastCollapsedRatType + " -> " + collapsedRatType); - } - // Protect service from frequently updating. Remove pending messages if any. - mHandler.removeMessages(MSG_UPDATE_IFACES); - mLastCollapsedRatType = collapsedRatType; - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_UPDATE_IFACES), mSettings.getPollDelay()); - } + @VisibleForTesting + public void handleOnCollapsedRatTypeChanged() { + // Protect service from frequently updating. Remove pending messages if any. + mHandler.removeMessages(MSG_UPDATE_IFACES); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_UPDATE_IFACES), mSettings.getPollDelay()); } private void updateIfaces( @@ -1352,8 +1341,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return 0; } - // TODO: return different subType for different subscriptions. - return mPhoneListener.mLastCollapsedRatType; + return mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(state.subscriberId); } private static <K> NetworkIdentitySet findOrCreateNetworkIdentitySet( diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index aed29272cada..e98326b620b2 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -30,6 +30,7 @@ import android.provider.Settings; import android.service.notification.Condition; import android.service.notification.ConditionProviderService; import android.service.notification.IConditionProvider; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -54,7 +55,6 @@ public class ConditionProviders extends ManagedServices { private final ArraySet<String> mSystemConditionProviderNames; private final ArraySet<SystemConditionProviderService> mSystemConditionProviders = new ArraySet<>(); - private Callback mCallback; public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) { @@ -195,6 +195,21 @@ public class ConditionProviders extends ManagedServices { } @Override + protected void loadDefaultsFromConfig() { + String defaultDndAccess = mContext.getResources().getString( + R.string.config_defaultDndAccessPackages); + if (defaultDndAccess != null) { + String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR); + for (int i = 0; i < dnds.length; i++) { + if (TextUtils.isEmpty(dnds[i])) { + continue; + } + addDefaultComponentOrPackage(dnds[i]); + } + } + } + + @Override protected void onServiceRemovedLocked(ManagedServiceInfo removed) { if (removed == null) return; for (int i = mRecords.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 45df3686d056..5d3dc5f19714 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -21,6 +21,7 @@ import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.os.UserHandle.USER_ALL; +import static android.os.UserHandle.USER_SYSTEM; import android.annotation.NonNull; import android.app.ActivityManager; @@ -96,6 +97,8 @@ abstract public class ManagedServices { private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000; protected static final String ENABLED_SERVICES_SEPARATOR = ":"; + private static final String DB_VERSION_1 = "1"; + /** * List of components and apps that can have running {@link ManagedServices}. @@ -107,7 +110,7 @@ abstract public class ManagedServices { static final String ATT_VERSION = "version"; static final String ATT_DEFAULTS = "defaults"; - static final int DB_VERSION = 1; + static final int DB_VERSION = 2; static final int APPROVAL_BY_PACKAGE = 0; static final int APPROVAL_BY_COMPONENT = 1; @@ -187,17 +190,22 @@ abstract public class ManagedServices { protected void addDefaultComponentOrPackage(String packageOrComponent) { if (!TextUtils.isEmpty(packageOrComponent)) { synchronized (mDefaultsLock) { - ComponentName cn = ComponentName.unflattenFromString(packageOrComponent); - if (cn == null) { + if (mApprovalLevel == APPROVAL_BY_PACKAGE) { mDefaultPackages.add(packageOrComponent); - } else { + return; + } + ComponentName cn = ComponentName.unflattenFromString(packageOrComponent); + if (cn != null && mApprovalLevel == APPROVAL_BY_COMPONENT) { mDefaultPackages.add(cn.getPackageName()); mDefaultComponents.add(cn); + return; } } } } + protected abstract void loadDefaultsFromConfig(); + boolean isDefaultComponentOrPackage(String packageOrComponent) { synchronized (mDefaultsLock) { ComponentName cn = ComponentName.unflattenFromString(packageOrComponent); @@ -504,19 +512,19 @@ abstract public class ManagedServices { void readDefaults(XmlPullParser parser) { String defaultComponents = XmlUtils.readStringAttribute(parser, ATT_DEFAULTS); - if (defaultComponents == null) { - return; - } - String[] components = defaultComponents.split(ENABLED_SERVICES_SEPARATOR); - synchronized (mDefaultsLock) { - for (int i = 0; i < components.length; i++) { - if (!TextUtils.isEmpty(components[i])) { - ComponentName cn = ComponentName.unflattenFromString(components[i]); - if (cn != null) { - mDefaultPackages.add(cn.getPackageName()); - mDefaultComponents.add(cn); - } else { - mDefaultPackages.add(components[i]); + + if (!TextUtils.isEmpty(defaultComponents)) { + String[] components = defaultComponents.split(ENABLED_SERVICES_SEPARATOR); + synchronized (mDefaultsLock) { + for (int i = 0; i < components.length; i++) { + if (!TextUtils.isEmpty(components[i])) { + ComponentName cn = ComponentName.unflattenFromString(components[i]); + if (cn != null) { + mDefaultPackages.add(cn.getPackageName()); + mDefaultComponents.add(cn); + } else { + mDefaultPackages.add(components[i]); + } } } } @@ -531,9 +539,11 @@ abstract public class ManagedServices { throws XmlPullParserException, IOException { // read grants int type; + String version = ""; readDefaults(parser); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { String tag = parser.getName(); + version = XmlUtils.readStringAttribute(parser, ATT_VERSION); if (type == XmlPullParser.END_TAG && getConfig().xmlTag.equals(tag)) { break; @@ -561,9 +571,38 @@ abstract public class ManagedServices { } } } + boolean isVersionOne = TextUtils.isEmpty(version) || DB_VERSION_1.equals(version); + if (isVersionOne) { + upgradeToVersionTwo(); + } rebindServices(false, USER_ALL); } + private void upgradeToVersionTwo() { + // check if any defaults are loaded + int defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); + if (defaultsSize == 0) { + // load defaults from current allowed + if (this.mApprovalLevel == APPROVAL_BY_COMPONENT) { + List<ComponentName> approvedComponents = getAllowedComponents(USER_SYSTEM); + for (int i = 0; i < approvedComponents.size(); i++) { + addDefaultComponentOrPackage(approvedComponents.get(i).flattenToString()); + } + } + if (this.mApprovalLevel == APPROVAL_BY_PACKAGE) { + List<String> approvedPkgs = getAllowedPackages(USER_SYSTEM); + for (int i = 0; i < approvedPkgs.size(); i++) { + addDefaultComponentOrPackage(approvedPkgs.get(i)); + } + } + } + // if no defaults are loaded, then load from config + defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); + if (defaultsSize == 0) { + loadDefaultsFromConfig(); + } + } + /** * Read extra attributes in the {@link #TAG_MANAGED_SERVICES} tag. */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 54efe543a29f..9b02b48f7825 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -108,6 +108,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -156,6 +157,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; @@ -220,6 +222,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseArrayMap; import android.util.StatsEvent; import android.util.Xml; import android.util.proto.ProtoOutputStream; @@ -291,6 +294,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.Objects; @@ -528,13 +532,15 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; - private static class Archive { + static class Archive { + final SparseArray<Boolean> mEnabled; final int mBufferSize; - final ArrayDeque<Pair<StatusBarNotification, Integer>> mBuffer; + final LinkedList<Pair<StatusBarNotification, Integer>> mBuffer; public Archive(int size) { mBufferSize = size; - mBuffer = new ArrayDeque<>(mBufferSize); + mBuffer = new LinkedList<>(); + mEnabled = new SparseArray<>(); } public String toString() { @@ -547,7 +553,10 @@ public class NotificationManagerService extends SystemService { return sb.toString(); } - public void record(StatusBarNotification nr, int reason) { + public void record(StatusBarNotification sbn, int reason) { + if (!mEnabled.get(sbn.getNormalizedUserId(), false)) { + return; + } if (mBuffer.size() == mBufferSize) { mBuffer.removeFirst(); } @@ -555,7 +564,7 @@ public class NotificationManagerService extends SystemService { // We don't want to store the heavy bits of the notification in the archive, // but other clients in the system process might be using the object, so we // store a (lightened) copy. - mBuffer.addLast(new Pair<>(nr.cloneLight(), reason)); + mBuffer.addLast(new Pair<>(sbn.cloneLight(), reason)); } public Iterator<Pair<StatusBarNotification, Integer>> descendingIterator() { @@ -577,60 +586,25 @@ public class NotificationManagerService extends SystemService { return a.toArray(new StatusBarNotification[a.size()]); } - } + public void updateHistoryEnabled(@UserIdInt int userId, boolean enabled) { + mEnabled.put(userId, enabled); - void loadDefaultApprovedServices(int userId) { - String defaultListenerAccess = getContext().getResources().getString( - com.android.internal.R.string.config_defaultListenerAccessPackages); - if (defaultListenerAccess != null) { - String[] listeners = - defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR); - for (int i = 0; i < listeners.length; i++) { - if (TextUtils.isEmpty(listeners[i])) { - continue; - } - ArraySet<ComponentName> approvedListeners = - mListeners.queryPackageForServices(listeners[i], - MATCH_DIRECT_BOOT_AWARE - | MATCH_DIRECT_BOOT_UNAWARE, userId); - for (int k = 0; k < approvedListeners.size(); k++) { - ComponentName cn = approvedListeners.valueAt(k); - mListeners.addDefaultComponentOrPackage(cn.flattenToString()); + if (!enabled) { + for (int i = mBuffer.size() - 1; i >= 0; i--) { + if (userId == mBuffer.get(i).first.getNormalizedUserId()) { + mBuffer.remove(i); + } } } } + } - String defaultDndAccess = getContext().getResources().getString( - com.android.internal.R.string.config_defaultDndAccessPackages); - if (defaultDndAccess != null) { - String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR); - for (int i = 0; i < dnds.length; i++) { - if (TextUtils.isEmpty(dnds[i])) { - continue; - } - mConditionProviders.addDefaultComponentOrPackage(dnds[i]); - } - } + void loadDefaultApprovedServices(int userId) { + mListeners.loadDefaultsFromConfig(); + mConditionProviders.loadDefaultsFromConfig(); - ArraySet<String> assistants = new ArraySet<>(); - String deviceAssistant = DeviceConfig.getProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE); - if (deviceAssistant != null) { - assistants.addAll(Arrays.asList(deviceAssistant.split( - ManagedServices.ENABLED_SERVICES_SEPARATOR))); - } - assistants.addAll(Arrays.asList(getContext().getResources().getString( - com.android.internal.R.string.config_defaultAssistantAccessComponent) - .split(ManagedServices.ENABLED_SERVICES_SEPARATOR))); - for (int i = 0; i < assistants.size(); i++) { - String cnString = assistants.valueAt(i); - if (TextUtils.isEmpty(cnString)) { - continue; - } - mAssistants.addDefaultComponentOrPackage(cnString); - } + mAssistants.loadDefaultsFromConfig(); } protected void allowDefaultApprovedServices(int userId) { @@ -653,11 +627,14 @@ public class NotificationManagerService extends SystemService { DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE); if (overrideDefaultAssistantString != null) { - ComponentName overrideDefaultAssistant = - ComponentName.unflattenFromString(overrideDefaultAssistantString); - if (allowAssistant(userId, overrideDefaultAssistant)) return; + ArraySet<ComponentName> approved = mAssistants.queryPackageForServices( + overrideDefaultAssistantString, + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, + userId); + for (int i = 0; i < approved.size(); i++) { + if (allowAssistant(userId, approved.valueAt(i))) return; + } } - ArraySet<ComponentName> defaults = mAssistants.getDefaultComponents(); // We should have only one default assistant by default // allowAssistant should execute once in practice @@ -1638,6 +1615,9 @@ public class NotificationManagerService extends SystemService { = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); private final Uri NOTIFICATION_RATE_LIMIT_URI = Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE); + private final Uri NOTIFICATION_HISTORY_ENABLED + = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_HISTORY_ENABLED); + SettingsObserver(Handler handler) { super(handler); @@ -1653,10 +1633,12 @@ public class NotificationManagerService extends SystemService { false, this, UserHandle.USER_ALL); resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(NOTIFICATION_HISTORY_ENABLED, + false, this, UserHandle.USER_ALL); update(null); } - @Override public void onChange(boolean selfChange, Uri uri) { + @Override public void onChange(boolean selfChange, Uri uri, int userId) { update(uri); } @@ -1681,6 +1663,14 @@ public class NotificationManagerService extends SystemService { if (uri == null || NOTIFICATION_BUBBLES_URI.equals(uri)) { mPreferencesHelper.updateBubblesEnabled(); } + if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) { + final IntArray userIds = mUserProfiles.getCurrentProfileIds(); + + for (int i = 0; i < userIds.size(); i++) { + mArchive.updateHistoryEnabled(userIds.get(i), Settings.Secure.getInt(resolver, + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0) == 1); + } + } } } @@ -1959,7 +1949,8 @@ public class NotificationManagerService extends SystemService { mPackageManagerClient, mRankingHandler, mZenModeHelper, - new NotificationChannelLoggerImpl()); + new NotificationChannelLoggerImpl(), + mAppOps); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, @@ -2300,7 +2291,8 @@ public class NotificationManagerService extends SystemService { mRoleObserver.init(); LauncherApps launcherApps = (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE); - mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener); + mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener, getLocalService( + ShortcutServiceInternal.class)); BubbleExtractor bubbsExtractor = mRankingHelper.findExtractor(BubbleExtractor.class); if (bubbsExtractor != null) { bubbsExtractor.setShortcutHelper(mShortcutHelper); @@ -8568,6 +8560,26 @@ public class NotificationManagerService extends SystemService { private ArrayMap<Integer, Boolean> mUserSetMap = new ArrayMap<>(); private Set<String> mAllowedAdjustments = new ArraySet<>(); + @Override + protected void loadDefaultsFromConfig() { + ArraySet<String> assistants = new ArraySet<>(); + assistants.addAll(Arrays.asList(mContext.getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessComponent) + .split(ManagedServices.ENABLED_SERVICES_SEPARATOR))); + for (int i = 0; i < assistants.size(); i++) { + String cnString = assistants.valueAt(i); + if (TextUtils.isEmpty(cnString)) { + continue; + } + ArraySet<ComponentName> approved = queryPackageForServices(cnString, + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM); + for (int k = 0; k < approved.size(); k++) { + ComponentName cn = approved.valueAt(k); + addDefaultComponentOrPackage(cn.flattenToString()); + } + } + } + public NotificationAssistants(Context context, Object lock, UserProfiles up, IPackageManager pm) { super(context, lock, up, pm); @@ -9005,7 +9017,29 @@ public class NotificationManagerService extends SystemService { public NotificationListeners(IPackageManager pm) { super(getContext(), mNotificationLock, mUserProfiles, pm); + } + @Override + protected void loadDefaultsFromConfig() { + String defaultListenerAccess = mContext.getResources().getString( + R.string.config_defaultListenerAccessPackages); + if (defaultListenerAccess != null) { + String[] listeners = + defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR); + for (int i = 0; i < listeners.length; i++) { + if (TextUtils.isEmpty(listeners[i])) { + continue; + } + ArraySet<ComponentName> approvedListeners = + this.queryPackageForServices(listeners[i], + MATCH_DIRECT_BOOT_AWARE + | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM); + for (int k = 0; k < approvedListeners.size(); k++) { + ComponentName cn = approvedListeners.valueAt(k); + addDefaultComponentOrPackage(cn.flattenToString()); + } + } + } } @Override diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 192df4139b37..2bbbffc203f4 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -590,6 +590,8 @@ public final class NotificationRecord { pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria())); } pw.println(prefix + "mAdjustments=" + mAdjustments); + pw.println(prefix + "shortcut=" + notification.getShortcutId() + + " found valid? " + (mShortcutInfo != null)); } @Override diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index b3d373ffab3a..d432fc83b52a 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -16,7 +16,9 @@ package com.android.server.notification; +import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; @@ -30,6 +32,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; @@ -79,7 +82,9 @@ import java.util.concurrent.ConcurrentHashMap; public class PreferencesHelper implements RankingConfig { private static final String TAG = "NotificationPrefHelper"; - private static final int XML_VERSION = 1; + private static final int XML_VERSION = 2; + /** What version to check to do the upgrade for bubbles. */ + private static final int XML_VERSION_BUBBLES_UPGRADE = 1; private static final int UNKNOWN_UID = UserHandle.USER_NULL; private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @@ -151,6 +156,7 @@ public class PreferencesHelper implements RankingConfig { private final RankingHandler mRankingHandler; private final ZenModeHelper mZenModeHelper; private final NotificationChannelLogger mNotificationChannelLogger; + private final AppOpsManager mAppOps; private SparseBooleanArray mBadgingEnabled; private boolean mBubblesEnabledGlobally = DEFAULT_GLOBAL_ALLOW_BUBBLE; @@ -167,12 +173,14 @@ public class PreferencesHelper implements RankingConfig { } public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, - ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger) { + ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger, + AppOpsManager appOpsManager) { mContext = context; mZenModeHelper = zenHelper; mRankingHandler = rankingHandler; mPm = pm; mNotificationChannelLogger = notificationChannelLogger; + mAppOps = appOpsManager; // STOPSHIP (b/142218092) this should be removed before ship if (!wasBadgingForcedTrue(context)) { @@ -195,6 +203,15 @@ public class PreferencesHelper implements RankingConfig { if (type != XmlPullParser.START_TAG) return; String tag = parser.getName(); if (!TAG_RANKING.equals(tag)) return; + + boolean upgradeForBubbles = false; + if (parser.getAttributeCount() > 0) { + String attribute = parser.getAttributeName(0); + if (ATT_VERSION.equals(attribute)) { + int xmlVersion = Integer.parseInt(parser.getAttributeValue(0)); + upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; + } + } synchronized (mPackagePreferences) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); @@ -220,6 +237,16 @@ public class PreferencesHelper implements RankingConfig { } } boolean skipWarningLogged = false; + boolean hasSAWPermission = false; + if (upgradeForBubbles) { + hasSAWPermission = mAppOps.noteOpNoThrow( + OP_SYSTEM_ALERT_WINDOW, uid, name, null, + "check-notif-bubble") == AppOpsManager.MODE_ALLOWED; + } + int bubblePref = hasSAWPermission + ? BUBBLE_PREFERENCE_ALL + : XmlUtils.readIntAttribute(parser, ATT_ALLOW_BUBBLE, + DEFAULT_BUBBLE_PREFERENCE); PackagePreferences r = getOrCreatePackagePreferencesLocked( name, userId, uid, @@ -231,8 +258,7 @@ public class PreferencesHelper implements RankingConfig { parser, ATT_VISIBILITY, DEFAULT_VISIBILITY), XmlUtils.readBooleanAttribute( parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE), - XmlUtils.readIntAttribute( - parser, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE)); + bubblePref); r.importance = XmlUtils.readIntAttribute( parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); r.priority = XmlUtils.readIntAttribute( diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java index f1ce3a7d9f48..1d4843822931 100644 --- a/services/core/java/com/android/server/notification/ShortcutHelper.java +++ b/services/core/java/com/android/server/notification/ShortcutHelper.java @@ -21,11 +21,15 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; import android.annotation.NonNull; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutServiceInternal; import android.os.Binder; import android.os.Handler; import android.os.UserHandle; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -38,6 +42,7 @@ import java.util.List; * Helper for querying shortcuts. */ class ShortcutHelper { + private static final String TAG = "ShortcutHelper"; /** * Listener to call when a shortcut we're tracking has been removed. @@ -48,6 +53,8 @@ class ShortcutHelper { private LauncherApps mLauncherAppsService; private ShortcutListener mShortcutListener; + private ShortcutServiceInternal mShortcutServiceInternal; + private IntentFilter mSharingFilter; // Key: packageName Value: <shortcutId, notifId> private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>(); @@ -111,9 +118,17 @@ class ShortcutHelper { } }; - ShortcutHelper(LauncherApps launcherApps, ShortcutListener listener) { + ShortcutHelper(LauncherApps launcherApps, ShortcutListener listener, + ShortcutServiceInternal shortcutServiceInternal) { mLauncherAppsService = launcherApps; mShortcutListener = listener; + mSharingFilter = new IntentFilter(); + try { + mSharingFilter.addDataType("*/*"); + } catch (IntentFilter.MalformedMimeTypeException e) { + Slog.e(TAG, "Bad mime type", e); + } + mShortcutServiceInternal = shortcutServiceInternal; } @VisibleForTesting @@ -121,6 +136,11 @@ class ShortcutHelper { mLauncherAppsService = launcherApps; } + @VisibleForTesting + void setShortcutServiceInternal(ShortcutServiceInternal shortcutServiceInternal) { + mShortcutServiceInternal = shortcutServiceInternal; + } + /** * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}. */ @@ -141,7 +161,14 @@ class ShortcutHelper { ShortcutInfo info = shortcuts != null && shortcuts.size() > 0 ? shortcuts.get(0) : null; - return info != null && info.isLongLived() ? info : null; + if (info == null || !info.isLongLived() || !info.isEnabled()) { + return null; + } + if (mShortcutServiceInternal.isSharingShortcut(user.getIdentifier(), + "android", packageName, shortcutId, user.getIdentifier(), mSharingFilter)) { + return info; + } + return null; } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index dab4bfd4df5a..5415967c3bdc 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -17,6 +17,7 @@ package com.android.server.pm; import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.Nullable; import android.app.job.JobInfo; @@ -434,7 +435,7 @@ public class BackgroundDexOptService extends JobService { | DexoptOptions.DEXOPT_DOWNGRADE; long package_size_before = getPackageSize(pm, pkg); - if (isForPrimaryDex) { + if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) { // This applies for system apps or if packages location is not a directory, i.e. // monolithic install. if (!pm.canHaveOatDir(pkg)) { @@ -486,7 +487,9 @@ public class BackgroundDexOptService extends JobService { | DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; - return isForPrimaryDex + // System server share the same code path as primary dex files. + // PackageManagerService will select the right optimization path for it. + return (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) ? performDexOptPrimary(pm, pkg, reason, dexoptFlags) : performDexOptSecondary(pm, pkg, reason, dexoptFlags); } diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 09baf6e0a817..ae9c38498b10 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -204,6 +204,12 @@ public class DataLoaderManagerService extends SystemService { @Override public void onServiceDisconnected(ComponentName arg0) { + if (mListener != null) { + try { + mListener.onStatusChanged(mId, IDataLoaderStatusListener.DATA_LOADER_DESTROYED); + } catch (RemoteException ignored) { + } + } remove(); } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 9fb468e8db6e..7cee286c4451 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -71,6 +71,8 @@ public class Installer extends SystemService { public static final int DEXOPT_GENERATE_COMPACT_DEX = 1 << 11; /** Indicates that dexopt should generate an app image */ public static final int DEXOPT_GENERATE_APP_IMAGE = 1 << 12; + /** Indicates that dexopt may be run with different performance / priority tuned for restore */ + public static final int DEXOPT_FOR_RESTORE = 1 << 13; // TODO(b/135202722): remove public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE; public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE; diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 65b7cf3eabd1..4b8a24204ca7 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -22,6 +22,7 @@ import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE; import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE; import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS; import static com.android.server.pm.Installer.DEXOPT_FORCE; +import static com.android.server.pm.Installer.DEXOPT_FOR_RESTORE; import static com.android.server.pm.Installer.DEXOPT_GENERATE_APP_IMAGE; import static com.android.server.pm.Installer.DEXOPT_GENERATE_COMPACT_DEX; import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB; @@ -32,6 +33,7 @@ import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE; import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT; import static com.android.server.pm.PackageManagerServiceCompilerMapping.getReasonName; @@ -115,7 +117,9 @@ public class PackageDexOptimizer { static boolean canOptimizePackage(AndroidPackage pkg) { // We do not dexopt a package with no code. - if (!pkg.isHasCode()) { + // Note that the system package is marked as having no code, however we can + // still optimize it via dexoptSystemServerPath. + if (!PLATFORM_PACKAGE_NAME.equals(pkg.getPackageName()) && !pkg.isHasCode()) { return false; } @@ -132,6 +136,10 @@ public class PackageDexOptimizer { int performDexOpt(AndroidPackage pkg, @NonNull PackageSetting pkgSetting, String[] instructionSets, CompilerStats.PackageStats packageStats, PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) { + if (PLATFORM_PACKAGE_NAME.equals(pkg.getPackageName())) { + throw new IllegalArgumentException("System server dexopting should be done via " + + " DexManager and PackageDexOptimizer#dexoptSystemServerPath"); + } if (pkg.getUid() == -1) { throw new IllegalArgumentException("Dexopt for " + pkg.getPackageName() + " has invalid uid."); @@ -699,6 +707,7 @@ public class PackageDexOptimizer { | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0) | (generateCompactDex ? DEXOPT_GENERATE_COMPACT_DEX : 0) | (generateAppImage ? DEXOPT_GENERATE_APP_IMAGE : 0) + | (options.isDexoptInstallForRestore() ? DEXOPT_FOR_RESTORE : 0) | hiddenApiFlag; return adjustDexoptFlags(dexFlags); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 59ac603875e2..d2481b758e82 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -66,6 +66,8 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE import static android.content.pm.PackageManager.INSTALL_INTERNAL; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE; +import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; @@ -94,6 +96,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.RESTRICTION_NONE; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; +import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.content.pm.PackageParser.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.incremental.IncrementalManager.isIncrementalPath; @@ -1820,10 +1823,12 @@ public class PackageManagerService extends IPackageManager.Stub state.setVerifierResponse(Binder.getCallingUid(), PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT); broadcastPackageVerified(verificationId, originUri, - PackageManager.VERIFICATION_ALLOW, user); + PackageManager.VERIFICATION_ALLOW, null, args.mDataLoaderType, + user); } else { broadcastPackageVerified(verificationId, originUri, - PackageManager.VERIFICATION_REJECT, user); + PackageManager.VERIFICATION_REJECT, null, args.mDataLoaderType, + user); params.setReturnCode( PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE); state.setVerifierResponse(Binder.getCallingUid(), @@ -1899,7 +1904,7 @@ public class PackageManagerService extends IPackageManager.Stub if (state.isInstallAllowed()) { broadcastPackageVerified(verificationId, originUri, - response.code, args.getUser()); + response.code, null, args.mDataLoaderType, args.getUser()); } else { params.setReturnCode( PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE); @@ -3576,7 +3581,8 @@ public class PackageManagerService extends IPackageManager.Stub // Prepare a supplier of package parser for the staging manager to parse apex file // during the staging installation. final Supplier<PackageParser2> apexParserSupplier = () -> new PackageParser2( - mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, mPackageParserCallback); + mSeparateProcesses, mOnlyCore, mMetrics, null /* cacheDir */, + mPackageParserCallback); mInstallerService = new PackageInstallerService(mContext, this, apexParserSupplier); final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr(); @@ -13575,12 +13581,17 @@ public class PackageManagerService extends IPackageManager.Stub } private void broadcastPackageVerified(int verificationId, Uri packageUri, - int verificationCode, UserHandle user) { + int verificationCode, @Nullable String rootHashString, int dataLoaderType, + UserHandle user) { final Intent intent = new Intent(Intent.ACTION_PACKAGE_VERIFIED); intent.setDataAndType(packageUri, PACKAGE_MIME_TYPE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId); intent.putExtra(PackageManager.EXTRA_VERIFICATION_RESULT, verificationCode); + if (rootHashString != null) { + intent.putExtra(PackageManager.EXTRA_VERIFICATION_ROOT_HASH, rootHashString); + } + intent.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); mContext.sendBroadcastAsUser(intent, user, android.Manifest.permission.PACKAGE_VERIFICATION_AGENT); @@ -14952,8 +14963,17 @@ public class PackageManagerService extends IPackageManager.Stub verificationState.setRequiredVerifierUid(requiredUid); final int installerUid = verificationInfo == null ? -1 : verificationInfo.installerUid; - if (!origin.existing && isVerificationEnabled(pkgLite, verifierUser.getIdentifier(), - installFlags, installerUid)) { + final boolean isVerificationEnabled = isVerificationEnabled( + pkgLite, verifierUser.getIdentifier(), installFlags, installerUid); + final boolean isV4Signed = + (mArgs.signingDetails.signatureSchemeVersion == SIGNING_BLOCK_V4); + final boolean isIncrementalInstall = + (mArgs.mDataLoaderType == DataLoaderType.INCREMENTAL); + // NOTE: We purposefully skip verification for only incremental installs when there's + // a v4 signature block. Otherwise, proceed with verification as usual. + if (!origin.existing + && isVerificationEnabled + && (!isIncrementalInstall || !isV4Signed)) { final Intent verification = new Intent( Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -16569,7 +16589,29 @@ public class PackageManagerService extends IPackageManager.Stub } executePostCommitSteps(commitRequest); } finally { - if (!success) { + if (success) { + for (InstallRequest request : requests) { + final InstallArgs args = request.args; + if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) { + continue; + } + if (args.signingDetails.signatureSchemeVersion != SIGNING_BLOCK_V4) { + continue; + } + // For incremental installs, we bypass the verifier prior to install. Now + // that we know the package is valid, send a notice to the verifier with + // the root hash of the base.apk. + final String baseCodePath = request.installResult.pkg.getBaseCodePath(); + final String[] splitCodePaths = request.installResult.pkg.getSplitCodePaths(); + final Uri originUri = Uri.fromFile(args.origin.resolvedFile); + final int verificationId = mPendingVerificationToken++; + final String rootHashString = PackageManagerServiceUtils + .buildVerificationRootHashString(baseCodePath, splitCodePaths); + broadcastPackageVerified(verificationId, originUri, + PackageManager.VERIFICATION_ALLOW, rootHashString, + args.mDataLoaderType, args.getUser()); + } + } else { for (ScanResult result : preparedScans.values()) { if (createdAppId.getOrDefault(result.request.parsedPackage.getPackageName(), false)) { @@ -16670,10 +16712,15 @@ public class PackageManagerService extends IPackageManager.Stub // method because `pkg` may not be in `mPackages` yet. // // Also, don't fail application installs if the dexopt step fails. + int flags = DexoptOptions.DEXOPT_BOOT_COMPLETE + | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE; + if (reconciledPkg.installArgs.installReason == INSTALL_REASON_DEVICE_RESTORE + || reconciledPkg.installArgs.installReason == INSTALL_REASON_DEVICE_SETUP) { + flags |= DexoptOptions.DEXOPT_FOR_RESTORE; + } DexoptOptions dexoptOptions = new DexoptOptions(packageName, REASON_INSTALL, - DexoptOptions.DEXOPT_BOOT_COMPLETE - | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE); + flags); ScanResult result = reconciledPkg.scanResult; // This mirrors logic from commitReconciledScanResultLocked, where the library files @@ -16911,7 +16958,6 @@ public class PackageManagerService extends IPackageManager.Stub if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) { parsedPackage.setSigningDetails(args.signingDetails); } else { - // TODO(b/136132412): skip for Incremental installation parsedPackage.setSigningDetails( ParsingPackageUtils.collectCertificates(parsedPackage, false /* skipVerify */)); } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 91afd846a9c3..5c175a6ef847 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -32,7 +32,6 @@ import android.annotation.Nullable; import android.app.AppGlobals; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -40,7 +39,6 @@ import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; -import android.content.pm.parsing.ParsingPackageUtils; import android.os.Build; import android.os.Debug; import android.os.Environment; @@ -50,6 +48,9 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManagerInternal; +import android.os.incremental.IncrementalManager; +import android.os.incremental.V4Signature; +import android.os.incremental.V4Signature.HashingInfo; import android.service.pm.PackageServiceDumpProto; import android.system.ErrnoException; import android.system.Os; @@ -62,6 +63,7 @@ import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.HexDump; import com.android.server.EventLogTags; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.PackageDexUsage; @@ -94,8 +96,6 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; @@ -943,4 +943,71 @@ public class PackageManagerServiceUtils { Os.chmod(currentDir.getAbsolutePath(), mode); } } + + /** + * Returns a string that's compatible with the verification root hash extra. + * @see PackageManager#EXTRA_VERIFICATION_ROOT_HASH + */ + @NonNull + public static String buildVerificationRootHashString(@NonNull String baseFilename, + @Nullable String[] splitFilenameArray) { + final StringBuilder sb = new StringBuilder(); + final String baseFilePath = + baseFilename.substring(baseFilename.lastIndexOf(File.separator) + 1); + sb.append(baseFilePath).append(":"); + final byte[] baseRootHash = getRootHash(baseFilename); + if (baseRootHash == null) { + sb.append("0"); + } else { + sb.append(HexDump.toHexString(baseRootHash)); + } + if (splitFilenameArray == null || splitFilenameArray.length == 0) { + return sb.toString(); + } + + for (int i = splitFilenameArray.length - 1; i >= 0; i--) { + final String splitFilename = splitFilenameArray[i]; + final String splitFilePath = + splitFilename.substring(splitFilename.lastIndexOf(File.separator) + 1); + final byte[] splitRootHash = getRootHash(splitFilename); + sb.append(";").append(splitFilePath).append(":"); + if (splitRootHash == null) { + sb.append("0"); + } else { + sb.append(HexDump.toHexString(splitRootHash)); + } + } + return sb.toString(); + } + + /** + * Returns the root has for the given file. + * <p>Otherwise, returns {@code null} if the root hash could not be found or calculated. + * <p>NOTE: This currently only works on files stored on the incremental file system. The + * eventual goal is that this hash [among others] can be retrieved for any file. + */ + @Nullable + private static byte[] getRootHash(String filename) { + try { + final byte[] baseFileSignature = + IncrementalManager.unsafeGetFileSignature(filename); + if (baseFileSignature == null) { + throw new IOException("File signature not present"); + } + final V4Signature signature = + V4Signature.readFrom(baseFileSignature); + if (signature.hashingInfo == null) { + throw new IOException("Hashing info not present"); + } + final HashingInfo hashInfo = + HashingInfo.fromByteArray(signature.hashingInfo); + if (ArrayUtils.isEmpty(hashInfo.rawRootHash)) { + throw new IOException("Root has not present"); + } + return hashInfo.rawRootHash; + } catch (IOException ignore) { + Slog.e(TAG, "ERROR: could not load root hash from incremental install"); + } + return null; + } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 323ffcfc2a1c..fc70af4e7bd4 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -103,6 +103,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; @@ -137,6 +138,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; /** * Service for {@link UserManager}. @@ -3244,16 +3246,39 @@ public class UserManagerService extends IUserManager.Stub { @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages) throws UserManager.CheckedUserOperationException { + final int nextProbableUserId = getNextAvailableId(); final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("createUser-" + flags); + final long sessionId = logUserCreateJourneyBegin(nextProbableUserId, userType, flags); try { return createUserInternalUncheckedNoTracing(name, userType, flags, parentId, preCreate, disallowedPackages, t); } finally { + logUserCreateJourneyFinish(sessionId, nextProbableUserId); t.traceEnd(); } } + private long logUserCreateJourneyBegin(@UserIdInt int userId, String userType, + @UserInfoFlag int flags) { + final long sessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE); + // log the journey atom with the user metadata + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId, + FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE, + /* origin_user= */ -1, userId, UserManager.getUserTypeForStatsd(userType), flags); + // log the event atom to indicate the event start + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId, + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER, + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN); + return sessionId; + } + + private void logUserCreateJourneyFinish(long sessionId, @UserIdInt int userId) { + FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId, + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER, + FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH); + } + private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages, diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index c0502b8a068c..0b6024a84f78 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -211,24 +211,16 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_BLUETOOTH_SHARING, - UserManager.DISALLOW_CONFIG_BLUETOOTH, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, - UserManager.DISALLOW_CONFIG_LOCATION, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, UserManager.DISALLOW_CONFIG_PRIVATE_DNS, UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_CONFIG_WIFI, - UserManager.DISALLOW_CONTENT_CAPTURE, - UserManager.DISALLOW_CONTENT_SUGGESTIONS, UserManager.DISALLOW_DATA_ROAMING, - UserManager.DISALLOW_DEBUGGING_FEATURES, UserManager.DISALLOW_SAFE_BOOT, - UserManager.DISALLOW_SHARE_LOCATION, UserManager.DISALLOW_SMS, UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_AIRPLANE_MODE, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, - UserManager.DISALLOW_OUTGOING_CALLS, UserManager.DISALLOW_UNMUTE_MICROPHONE ); @@ -237,7 +229,16 @@ public class UserRestrictionsUtils { * set on the parent profile instance to apply them on the personal profile. */ private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS = - Sets.newArraySet(); + Sets.newArraySet( + UserManager.DISALLOW_CONFIG_BLUETOOTH, + UserManager.DISALLOW_CONFIG_LOCATION, + UserManager.DISALLOW_CONFIG_WIFI, + UserManager.DISALLOW_CONTENT_CAPTURE, + UserManager.DISALLOW_CONTENT_SUGGESTIONS, + UserManager.DISALLOW_DEBUGGING_FEATURES, + UserManager.DISALLOW_SHARE_LOCATION, + UserManager.DISALLOW_OUTGOING_CALLS + ); /** * User restrictions that default to {@code true} for managed profile owners. diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 6dcf71e9fbf0..f7bf1d985786 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -79,6 +79,10 @@ public class DexManager { private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = "pm.dexopt.priv-apps-oob-list"; + // System server cannot load executable code outside system partitions. + // However it can load verification data - thus we pick the "verify" compiler filter. + private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify"; + private final Context mContext; // Maps package name to code locations. @@ -443,6 +447,14 @@ public class DexManager { * because they don't need to be compiled).. */ public boolean dexoptSecondaryDex(DexoptOptions options) { + if (PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) { + // We could easily redirect to #dexoptSystemServer in this case. But there should be + // no-one calling this method directly for system server. + // As such we prefer to abort in this case. + Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer"); + return false; + } + PackageDexOptimizer pdo = getPackageDexOptimizer(options); String packageName = options.getPackageName(); PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); @@ -501,8 +513,17 @@ public class DexManager { return PackageDexOptimizer.DEX_OPT_FAILED; } - PackageDexOptimizer pdo = getPackageDexOptimizer(options); - String packageName = options.getPackageName(); + // Override compiler filter for system server to the expected one. + // + // We could let the caller do this every time the invoke PackageManagerServer#dexopt. + // However, there are a few places were this will need to be done which creates + // redundancy and the danger of overlooking the config (and thus generating code that will + // waste storage and time). + DexoptOptions overriddenOptions = options.overrideCompilerFilter( + SYSTEM_SERVER_COMPILER_FILTER); + + PackageDexOptimizer pdo = getPackageDexOptimizer(overriddenOptions); + String packageName = overriddenOptions.getPackageName(); PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); if (useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { @@ -527,7 +548,7 @@ public class DexManager { continue; } - int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, options); + int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, overriddenOptions); // The end result is: // - FAILED if any path failed, @@ -600,6 +621,23 @@ public class DexManager { packageName, dexUseInfo.getOwnerUserId()) || updated; continue; } + + // Special handle system server files. + // We don't need an installd call because we have permissions to check if the file + // exists. + if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + if (!Files.exists(Paths.get(dexPath))) { + if (DEBUG) { + Slog.w(TAG, "A dex file previously loaded by System Server does not exist " + + " anymore: " + dexPath); + } + updated = mPackageDexUsage.removeUserPackage( + packageName, dexUseInfo.getOwnerUserId()) || updated; + } + continue; + } + + // This is a regular application. ApplicationInfo info = pkg.applicationInfo; int flags = 0; if (info.deviceProtectedDataDir != null && diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java index de3c9f28218d..68f38861d7e4 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java +++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java @@ -61,6 +61,10 @@ public final class DexoptOptions { // should get the dex metdata file if present. public static final int DEXOPT_INSTALL_WITH_DEX_METADATA_FILE = 1 << 10; + // When set, indicates that dexopt is being invoked from the install flow during device restore + // or device setup and should be scheduled appropriately. + public static final int DEXOPT_FOR_RESTORE = 1 << 11; // TODO(b/135202722): remove + // The name of package to optimize. private final String mPackageName; @@ -99,7 +103,8 @@ public final class DexoptOptions { DEXOPT_DOWNGRADE | DEXOPT_AS_SHARED_LIBRARY | DEXOPT_IDLE_BACKGROUND_JOB | - DEXOPT_INSTALL_WITH_DEX_METADATA_FILE; + DEXOPT_INSTALL_WITH_DEX_METADATA_FILE | + DEXOPT_FOR_RESTORE; if ((flags & (~validityMask)) != 0) { throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags)); } @@ -155,6 +160,10 @@ public final class DexoptOptions { return (mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) != 0; } + public boolean isDexoptInstallForRestore() { + return (mFlags & DEXOPT_FOR_RESTORE) != 0; + } + public String getSplitName() { return mSplitName; } @@ -166,4 +175,17 @@ public final class DexoptOptions { public int getCompilationReason() { return mCompilationReason; } + + /** + * Creates a new set of DexoptOptions which are the same with the exception of the compiler + * filter (set to the given value). + */ + public DexoptOptions overrideCompilerFilter(String newCompilerFilter) { + return new DexoptOptions( + mPackageName, + mCompilationReason, + newCompilerFilter, + mSplitName, + mFlags); + } } diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 1c4568095ce3..4bbe3733719e 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -81,9 +81,6 @@ public class OneTimePermissionUserManager { mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); - - // Listen for tracked uid being uninstalled - context.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); } /** @@ -171,6 +168,14 @@ public class OneTimePermissionUserManager { } /** + * Register to listen for Uids being uninstalled. This must be done outside of the + * PermissionManagerService lock. + */ + void registerUninstallListener() { + mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); + } + + /** * A class which watches a package for inactivity and notifies the permission controller when * the package becomes inactive */ diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index ccc749232dc3..bacc7acf3dc7 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -26,6 +26,7 @@ import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; @@ -1656,7 +1657,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int userSettableMask = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_REVOKED_COMPAT - | FLAG_PERMISSION_REVIEW_REQUIRED; + | FLAG_PERMISSION_REVIEW_REQUIRED + | FLAG_PERMISSION_ONE_TIME; final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED; @@ -3210,16 +3212,19 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private OneTimePermissionUserManager getOneTimePermissionUserManager(@UserIdInt int userId) { + OneTimePermissionUserManager oneTimePermissionUserManager; synchronized (mLock) { - OneTimePermissionUserManager oneTimePermissionUserManager = + oneTimePermissionUserManager = mOneTimePermissionUserManagers.get(userId); - if (oneTimePermissionUserManager == null) { - oneTimePermissionUserManager = new OneTimePermissionUserManager( - mContext.createContextAsUser(UserHandle.of(userId), /*flags*/ 0)); - mOneTimePermissionUserManagers.put(userId, oneTimePermissionUserManager); + if (oneTimePermissionUserManager != null) { + return oneTimePermissionUserManager; } - return oneTimePermissionUserManager; + oneTimePermissionUserManager = new OneTimePermissionUserManager( + mContext.createContextAsUser(UserHandle.of(userId), /*flags*/ 0)); + mOneTimePermissionUserManagers.put(userId, oneTimePermissionUserManager); } + oneTimePermissionUserManager.registerUninstallListener(); + return oneTimePermissionUserManager; } @Override diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 5a3464d8a35f..764ac969e188 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2338,8 +2338,7 @@ public final class PowerManagerService extends SystemService nextTimeout = -1; } - if (((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 - || (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0) + if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 && (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) == 0) { nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout, screenDimDuration); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java index 870d909fbbcc..a18b690f08cd 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java @@ -33,7 +33,7 @@ import java.util.Map; * This is not necessarily a strict enforcement for the HAL contract, but a place to add checks for * common HAL malfunctions, to help track them and assist in debugging. * - * The class is not thread-safe. + * The class is thread-safe. */ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { static final String TAG = "SoundTriggerHw2Enforcer"; @@ -55,7 +55,9 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback, int cookie) { int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback), cookie); - mModelStates.put(handle, false); + synchronized (mModelStates) { + mModelStates.put(handle, false); + } return handle; } @@ -64,27 +66,35 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { int cookie) { int handle = mUnderlying.loadPhraseSoundModel(soundModel, new CallbackEnforcer(callback), cookie); - mModelStates.put(handle, false); + synchronized (mModelStates) { + mModelStates.put(handle, false); + } return handle; } @Override public void unloadSoundModel(int modelHandle) { mUnderlying.unloadSoundModel(modelHandle); - mModelStates.remove(modelHandle); + synchronized (mModelStates) { + mModelStates.remove(modelHandle); + } } @Override public void stopRecognition(int modelHandle) { mUnderlying.stopRecognition(modelHandle); - mModelStates.replace(modelHandle, false); + synchronized (mModelStates) { + mModelStates.replace(modelHandle, false); + } } @Override public void stopAllRecognitions() { mUnderlying.stopAllRecognitions(); - for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) { - entry.setValue(false); + synchronized (mModelStates) { + for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) { + entry.setValue(false); + } } } @@ -92,7 +102,9 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback, int cookie) { mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback), cookie); - mModelStates.replace(modelHandle, true); + synchronized (mModelStates) { + mModelStates.replace(modelHandle, true); + } } @Override @@ -142,12 +154,14 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { public void recognitionCallback(ISoundTriggerHwCallback.RecognitionEvent event, int cookie) { int model = event.header.model; - if (!mModelStates.getOrDefault(model, false)) { - Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); - } - if (event.header.status - != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { - mModelStates.replace(model, false); + synchronized (mModelStates) { + if (!mModelStates.getOrDefault(model, false)) { + Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + } + if (event.header.status + != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { + mModelStates.replace(model, false); + } } mUnderlying.recognitionCallback(event, cookie); } @@ -156,12 +170,14 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { public void phraseRecognitionCallback(ISoundTriggerHwCallback.PhraseRecognitionEvent event, int cookie) { int model = event.common.header.model; - if (!mModelStates.getOrDefault(model, false)) { - Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); - } - if (event.common.header.status - != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { - mModelStates.replace(model, false); + synchronized (mModelStates) { + if (!mModelStates.getOrDefault(model, false)) { + Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + } + if (event.common.header.status + != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { + mModelStates.replace(model, false); + } } mUnderlying.phraseRecognitionCallback(event, cookie); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index 04ba6bfeb4ee..8b6ed1ff5081 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -377,11 +377,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt } private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) { - if (obj instanceof Parcelable) { - ObjectPrinter.print(builder, obj, true, 16); - } else { - builder.append(obj.toString()); - } + ObjectPrinter.print(builder, obj, true, 16); } private static String printObject(@Nullable Object obj) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 23259558eeb7..bae244179346 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -32,9 +32,9 @@ import android.media.soundtrigger_middleware.RecognitionEvent; import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; +import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; import android.media.soundtrigger_middleware.Status; import android.os.IBinder; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -108,17 +108,26 @@ import java.util.Set; public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareInternal, Dumpable { private static final String TAG = "SoundTriggerMiddlewareValidation"; - private enum ModuleState { + private enum ModuleStatus { ALIVE, DETACHED, DEAD }; + private class ModuleState { + final @NonNull SoundTriggerModuleProperties properties; + Set<ModuleService> sessions = new HashSet<>(); + + private ModuleState(@NonNull SoundTriggerModuleProperties properties) { + this.properties = properties; + } + } + private Boolean mCaptureState; private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; private final @NonNull Context mContext; - private Map<Integer, Set<ModuleService>> mModules; + private Map<Integer, ModuleState> mModules; public SoundTriggerMiddlewareValidation( @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) { @@ -168,7 +177,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware SoundTriggerModuleDescriptor[] result = mDelegate.listModules(); mModules = new HashMap<>(result.length); for (SoundTriggerModuleDescriptor desc : result) { - mModules.put(desc.handle, new HashSet<>()); + mModules.put(desc.handle, new ModuleState(desc.properties)); } return result; } catch (Exception e) { @@ -278,18 +287,21 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware @Override public void dump(PrintWriter pw) { synchronized (this) { - pw.printf("Capture state is %s\n", mCaptureState == null ? "uninitialized" + pw.printf("Capture state is %s\n\n", mCaptureState == null ? "uninitialized" : (mCaptureState ? "active" : "inactive")); if (mModules != null) { for (int handle : mModules.keySet()) { + final ModuleState module = mModules.get(handle); pw.println("========================================="); - pw.printf("Active sessions for module %d", handle); - pw.println(); + pw.printf("Module %d\n%s\n", handle, + ObjectPrinter.print(module.properties, true, 16)); pw.println("========================================="); - for (ModuleService session : mModules.get(handle)) { + for (ModuleService session : module.sessions) { session.dump(pw); } } + } else { + pw.println("Modules have not yet been enumerated."); } } pw.println(); @@ -297,11 +309,18 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware if (mDelegate instanceof Dumpable) { ((Dumpable) mDelegate).dump(pw); } - } /** State of a sound model. */ static class ModelState { + ModelState(SoundModel model) { + this.description = ObjectPrinter.print(model, true, 16); + } + + ModelState(PhraseSoundModel model) { + this.description = ObjectPrinter.print(model, true, 16); + } + /** Activity state of a sound model. */ enum Activity { /** Model is loaded, recognition is inactive. */ @@ -313,6 +332,9 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware /** Activity state. */ Activity activityState = Activity.LOADED; + /** Human-readable description of the model. */ + final String description; + /** * A map of known parameter support. A missing key means we don't know yet whether the * parameter is supported. A null value means it is known to not be supported. A non-null @@ -375,7 +397,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware private ISoundTriggerModule mDelegate; private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>(); private final int mHandle; - private ModuleState mState = ModuleState.ALIVE; + private ModuleStatus mState = ModuleStatus.ALIVE; ModuleService(int handle, @NonNull ISoundTriggerCallback callback) { mCallback = callback; @@ -389,7 +411,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware void attach(@NonNull ISoundTriggerModule delegate) { mDelegate = delegate; - mModules.get(mHandle).add(this); + mModules.get(mHandle).sessions.add(this); } @Override @@ -401,14 +423,14 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } // From here on, every exception isn't client's fault. try { int handle = mDelegate.loadModel(model); - mLoadedModels.put(handle, new ModelState()); + mLoadedModels.put(handle, new ModelState(model)); return handle; } catch (Exception e) { throw handleException(e); @@ -425,14 +447,14 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } // From here on, every exception isn't client's fault. try { int handle = mDelegate.loadPhraseModel(model); - mLoadedModels.put(handle, new ModelState()); + mLoadedModels.put(handle, new ModelState(model)); return handle; } catch (Exception e) { throw handleException(e); @@ -448,7 +470,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -481,7 +503,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -515,7 +537,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -544,7 +566,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -572,7 +594,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -600,7 +622,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -629,7 +651,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has been detached."); } ModelState modelState = mLoadedModels.get( @@ -658,10 +680,10 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware synchronized (SoundTriggerMiddlewareValidation.this) { // State validation. - if (mState == ModuleState.DETACHED) { + if (mState == ModuleStatus.DETACHED) { throw new IllegalStateException("Module has already been detached."); } - if (mState == ModuleState.ALIVE && !mLoadedModels.isEmpty()) { + if (mState == ModuleStatus.ALIVE && !mLoadedModels.isEmpty()) { throw new IllegalStateException("Cannot detach while models are loaded."); } @@ -683,16 +705,16 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware private void detachInternal() { try { mDelegate.detach(); - mState = ModuleState.DETACHED; + mState = ModuleStatus.DETACHED; mCallback.asBinder().unlinkToDeath(this, 0); - mModules.get(mHandle).remove(this); + mModules.get(mHandle).sessions.remove(this); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } void dump(PrintWriter pw) { - if (mState == ModuleState.ALIVE) { + if (mState == ModuleStatus.ALIVE) { pw.printf("Loaded models for session %s (handle, active)", toString()); pw.println(); pw.println("-------------------------------"); @@ -700,6 +722,8 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware pw.print(entry.getKey()); pw.print('\t'); pw.print(entry.getValue().activityState.name()); + pw.print('\t'); + pw.print(entry.getValue().description); pw.println(); } } else { @@ -762,7 +786,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware public void onModuleDied() { synchronized (SoundTriggerMiddlewareValidation.this) { try { - mState = ModuleState.DEAD; + mState = ModuleStatus.DEAD; mCallback.onModuleDied(); } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java index 67677c6cf17e..e1e6195ad260 100644 --- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java @@ -41,10 +41,11 @@ public final class ProcfsMemoryUtil { public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) { long[] output = new long[STATUS_KEYS.length]; output[0] = -1; + output[3] = -1; + output[4] = -1; Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output); - if (output[0] == -1 || (output[3] == 0 && output[4] == 0)) { - // Could not open file or anon rss / swap are 0 indicating the process is in a zombie - // state. + if (output[0] == -1 || output[3] == -1 || output[4] == -1) { + // Could not open or parse file. return null; } final MemorySnapshot snapshot = new MemorySnapshot(); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 288c22a94b45..1afec9c18a7f 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -2059,32 +2059,35 @@ public class StatsPullAtomService extends SystemService { synchronized (mProcessStatsLock) { final long token = Binder.clearCallingIdentity(); try { + // force procstats to flush & combine old files into one store long lastHighWaterMark = readProcStatsHighWaterMark(section); List<ParcelFileDescriptor> statsFiles = new ArrayList<>(); - long highWaterMark = processStatsService.getCommittedStats( - lastHighWaterMark, section, true, statsFiles); - if (statsFiles.size() != 1) { - return StatsManager.PULL_SKIP; - } - unpackStreamedData(atomTag, pulledData, statsFiles); + + ProcessStats procStats = new ProcessStats(false); + long highWaterMark = processStatsService.getCommittedStatsMerged( + lastHighWaterMark, section, true, statsFiles, procStats); + + // aggregate the data together for westworld consumption + ProtoOutputStream proto = new ProtoOutputStream(); + procStats.dumpAggregatedProtoForStatsd(proto); + + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeByteArray(proto.getBytes()) + .build(); + pulledData.add(e); + new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark) .delete(); new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + highWaterMark) .createNewFile(); - } catch (IOException e) { - Slog.e(TAG, "Getting procstats failed: ", e); - return StatsManager.PULL_SKIP; - } catch (RemoteException e) { - Slog.e(TAG, "Getting procstats failed: ", e); - return StatsManager.PULL_SKIP; - } catch (SecurityException e) { + } catch (RemoteException | IOException e) { Slog.e(TAG, "Getting procstats failed: ", e); return StatsManager.PULL_SKIP; } finally { Binder.restoreCallingIdentity(token); } } - return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java index 06c2354c7a7d..f59d431d4382 100644 --- a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java +++ b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java @@ -27,6 +27,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.UserHandle; +import android.text.TextUtils.SimpleStringSplitter; import android.util.Log; import android.util.Slog; @@ -34,6 +35,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * Watches for emote provider services to be installed. @@ -51,8 +54,8 @@ final class TvRemoteProviderWatcher { private final PackageManager mPackageManager; private final ArrayList<TvRemoteProviderProxy> mProviderProxies = new ArrayList<>(); private final int mUserId; - private final String mUnbundledServicePackage; private final Object mLock; + private final Set<String> mUnbundledServicePackages = new HashSet<>(); private boolean mRunning; @@ -61,9 +64,19 @@ final class TvRemoteProviderWatcher { mHandler = new Handler(true); mUserId = UserHandle.myUserId(); mPackageManager = context.getPackageManager(); - mUnbundledServicePackage = context.getString( - com.android.internal.R.string.config_tvRemoteServicePackage); mLock = lock; + + // Unbundled package names supports a comma-separated list + SimpleStringSplitter splitter = new SimpleStringSplitter(','); + splitter.setString(context.getString( + com.android.internal.R.string.config_tvRemoteServicePackage)); + + splitter.forEach(packageName -> { + packageName = packageName.trim(); + if (!packageName.isEmpty()) { + mUnbundledServicePackages.add(packageName); + } + }); } public void start() { @@ -157,7 +170,7 @@ final class TvRemoteProviderWatcher { } // Check if package name is white-listed here. - if (!serviceInfo.packageName.equals(mUnbundledServicePackage)) { + if (!mUnbundledServicePackages.contains(serviceInfo.packageName)) { Slog.w(TAG, "Ignoring atv remote provider service because the package has not " + "been set and/or whitelisted: " + serviceInfo.packageName + "/" + serviceInfo.name); diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index e100ff816f00..dfd23df40374 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -131,7 +131,7 @@ public final class ClientProfile { mUsingFrontendIds.add(frontendId); } - public Iterable<Integer> getInUseFrontendIds() { + public Set<Integer> getInUseFrontendIds() { return mUsingFrontendIds; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 6dded00321b5..fd2445f8366e 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -243,15 +243,20 @@ public class TunerResourceManagerService extends SystemService { if (DEBUG) { Slog.d(TAG, "requestLnb(request=" + request + ")"); } + return true; } @Override - public void releaseFrontend(int frontendId) { + public void releaseFrontend(int frontendHandle) throws RemoteException { enforceTunerAccessPermission("releaseFrontend"); enforceTrmAccessPermission("releaseFrontend"); - if (DEBUG) { - Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")"); + if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, + frontendHandle)) { + throw new RemoteException("frontendHandle can't be invalid"); + } + synchronized (mLock) { + releaseFrontendInternal(getResourceIdFromHandle(frontendHandle)); } } @@ -393,7 +398,6 @@ public class TunerResourceManagerService extends SystemService { } } - // TODO check if the removing resource is in use or not. Handle the conflict. for (int removingId : updatingFrontendIds) { // update the exclusive group id member list removeFrontendResource(removingId); @@ -464,6 +468,14 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting + void releaseFrontendInternal(int frontendId) { + if (DEBUG) { + Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")"); + } + updateFrontendClientMappingOnRelease(frontendId); + } + + @VisibleForTesting boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { if (DEBUG) { Slog.d(TAG, "requestDemux(request=" + request + ")"); @@ -568,6 +580,17 @@ public class TunerResourceManagerService extends SystemService { } } + private void updateFrontendClientMappingOnRelease(int frontendId) { + FrontendResource releasingFrontend = getFrontendResource(frontendId); + ClientProfile ownerProfile = getClientProfile(releasingFrontend.getOwnerClientId()); + releasingFrontend.removeOwner(); + ownerProfile.releaseFrontend(frontendId); + for (int exclusiveGroupMember : releasingFrontend.getExclusiveGroupMemberFeIds()) { + getFrontendResource(exclusiveGroupMember).removeOwner(); + ownerProfile.releaseFrontend(exclusiveGroupMember); + } + } + /** * Get the owner client's priority from the frontend id. * @@ -609,6 +632,9 @@ public class TunerResourceManagerService extends SystemService { private void removeFrontendResource(int removingId) { FrontendResource fe = getFrontendResource(removingId); + if (fe.isInUse()) { + releaseFrontendInternal(removingId); + } for (int excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { getFrontendResource(excGroupmemberFeId) .removeExclusiveGroupMemberFeId(fe.getId()); @@ -651,6 +677,22 @@ public class TunerResourceManagerService extends SystemService { | (mResourceRequestCount++ & 0xffff); } + @VisibleForTesting + protected int getResourceIdFromHandle(int resourceHandle) { + if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) { + return resourceHandle; + } + return (resourceHandle & 0x00ff0000) >> 16; + } + + private boolean validateResourceHandle(int resourceType, int resourceHandle) { + if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE + || ((resourceHandle & 0xff000000) >> 24) != resourceType) { + return false; + } + return true; + } + private void enforceTrmAccessPermission(String apiName) { getContext().enforceCallingPermission("android.permission.TUNER_RESOURCE_ACCESS", TAG + ": " + apiName); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 75d6a090bd3d..4fe58433ddb6 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -674,6 +674,15 @@ final class AccessibilityController { availableBounds.op(windowBounds, Region.Op.DIFFERENCE); } + // If the navigation bar window doesn't have touchable region, count + // navigation bar insets into nonMagnifiedBounds. It happens when + // navigation mode is gestural. + if (isUntouchableNavigationBar(windowState, mTempRegion3)) { + final Rect navBarInsets = getNavBarInsets(mDisplayContent); + nonMagnifiedBounds.op(navBarInsets, Region.Op.UNION); + availableBounds.op(navBarInsets, Region.Op.DIFFERENCE); + } + // Count letterbox into nonMagnifiedBounds if (windowState.isLetterboxedForDisplayCutoutLw()) { Region letterboxBounds = getLetterboxBounds(windowState); @@ -1091,6 +1100,24 @@ final class AccessibilityController { } } + static boolean isUntouchableNavigationBar(WindowState windowState, + Region touchableRegion) { + if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) { + return false; + } + + // Gets the touchable region. + windowState.getTouchableRegion(touchableRegion); + + return touchableRegion.isEmpty(); + } + + static Rect getNavBarInsets(DisplayContent displayContent) { + final InsetsState insetsState = + displayContent.getInsetsStateController().getRawInsetsState(); + return insetsState.getSource(ITYPE_NAVIGATION_BAR).getFrame(); + } + /** * This class encapsulates the functionality related to computing the windows * reported for accessibility purposes. These windows are all windows a sighted @@ -1205,16 +1232,12 @@ final class AccessibilityController { updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace, skipRemainingWindowsForTasks); focusedWindowAdded |= windowState.isFocused(); - } else if (isUntouchableNavigationBar(windowState)) { + } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) { // If this widow is navigation bar without touchable region, accounting the // region of navigation bar inset because all touch events from this region // would be received by launcher, i.e. this region is a un-touchable one // for the application. - final InsetsState insetsState = - dc.getInsetsStateController().getRawInsetsState(); - final Rect displayFrame = - insetsState.getSource(ITYPE_NAVIGATION_BAR).getFrame(); - unaccountedSpace.op(displayFrame, unaccountedSpace, + unaccountedSpace.op(getNavBarInsets(dc), unaccountedSpace, Region.Op.REVERSE_DIFFERENCE); } @@ -1294,18 +1317,6 @@ final class AccessibilityController { return false; } - private boolean isUntouchableNavigationBar(WindowState windowState) { - if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) { - return false; - } - - // Gets the touchable region. - Region touchableRegion = mTempRegion1; - windowState.getTouchableRegion(touchableRegion); - - return touchableRegion.isEmpty(); - } - private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen, Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) { if (windowState.mAttrs.type diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 90dd9c6c6063..a03beee3a32f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1377,8 +1377,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect spaceToFill = transformedBounds != null ? transformedBounds : inMultiWindowMode() - ? task.getDisplayedBounds() - : getRootTask().getParent().getDisplayedBounds(); + ? task.getBounds() + : getRootTask().getParent().getBounds(); mLetterbox.layout(spaceToFill, w.getFrameLw(), mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); @@ -6670,17 +6670,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return super.getBounds(); } - @Override - Rect getDisplayedBounds() { - if (task != null) { - final Rect overrideDisplayedBounds = task.getOverrideDisplayedBounds(); - if (!overrideDisplayedBounds.isEmpty()) { - return overrideDisplayedBounds; - } - } - return getBounds(); - } - @VisibleForTesting @Override Rect getAnimationBounds(int appStackClipMode) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 8bf46bc7c2e8..5968eede0a27 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -91,7 +91,6 @@ import static com.android.server.wm.TaskProto.ANIMATING_BOUNDS; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; import static com.android.server.wm.TaskProto.DEFER_REMOVAL; -import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS; import static com.android.server.wm.TaskProto.DISPLAY_ID; import static com.android.server.wm.TaskProto.FILLS_PARENT; import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS; @@ -660,8 +659,7 @@ class ActivityStack extends Task { setBounds(newBounds); } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) { // For pinned stack, resize is now part of the {@link WindowContainerTransaction} - resize(new Rect(newBounds), null /* configBounds */, - PRESERVE_WINDOWS, true /* deferResume */); + resize(new Rect(newBounds), PRESERVE_WINDOWS, true /* deferResume */); } } if (prevIsAlwaysOnTop != isAlwaysOnTop()) { @@ -835,8 +833,7 @@ class ActivityStack extends Task { } if (!Objects.equals(getRequestedOverrideBounds(), mTmpRect2)) { - resize(mTmpRect2, null /*configBounds*/, - false /*preserveWindows*/, true /*deferResume*/); + resize(mTmpRect2, false /*preserveWindows*/, true /*deferResume*/); } } finally { mAtmService.continueWindowLayout(); @@ -894,9 +891,6 @@ class ActivityStack extends Task { setTaskBounds(mDeferredBounds); setBounds(mDeferredBounds); } - if (mUpdateDisplayedBoundsDeferredCalled) { - setTaskDisplayedBounds(mDeferredDisplayedBounds); - } } } @@ -2966,8 +2960,7 @@ class ActivityStack extends Task { // TODO: Can only be called from special methods in ActivityStackSupervisor. // Need to consolidate those calls points into this resize method so anyone can call directly. - void resize(Rect displayedBounds, Rect configBounds, boolean preserveWindows, - boolean deferResume) { + void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) { if (!updateBoundsAllowed(displayedBounds)) { return; } @@ -2979,7 +2972,7 @@ class ActivityStack extends Task { // Update override configurations of all tasks in the stack. final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class), - displayedBounds, configBounds); + displayedBounds); forAllTasks(c, true /* traverseTopToBottom */); c.recycle(); @@ -3000,17 +2993,10 @@ class ActivityStack extends Task { } } - private static void processTaskResizeBounds( - Task task, Rect displayedBounds, Rect configBounds) { + private static void processTaskResizeBounds(Task task, Rect displayedBounds) { if (!task.isResizeable()) return; - if (configBounds != null && !configBounds.isEmpty()) { - task.setOverrideDisplayedBounds(displayedBounds); - task.setBounds(configBounds); - } else { - task.setOverrideDisplayedBounds(null); - task.setBounds(displayedBounds); - } + task.setBounds(displayedBounds); } /** @@ -3032,22 +3018,6 @@ class ActivityStack extends Task { task.setBounds(task.isResizeable() ? bounds : null); } - /** Helper to setDisplayedBounds on all child tasks */ - private void setTaskDisplayedBounds(Rect bounds) { - if (!updateDisplayedBoundsAllowed(bounds)) { - return; - } - - final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds, - PooledLambda.__(Task.class), bounds); - forAllLeafTasks(c, true /* traverseTopToBottom */); - c.recycle(); - } - - private static void setTaskDisplayedBounds(Task task, Rect bounds) { - task.setOverrideDisplayedBounds(bounds == null || bounds.isEmpty() ? null : bounds); - } - /** * Returns the top-most activity that occludes the given one, or @{code null} if none. */ @@ -3569,8 +3539,8 @@ class ActivityStack extends Task { } @Override - void getRelativeDisplayedPosition(Point outPos) { - super.getRelativeDisplayedPosition(outPos); + void getRelativePosition(Point outPos) { + super.getRelativePosition(outPos); final int outset = getStackOutset(); outPos.x -= outset; outPos.y -= outset; @@ -3581,7 +3551,7 @@ class ActivityStack extends Task { return; } - final Rect stackBounds = getDisplayedBounds(); + final Rect stackBounds = getBounds(); int width = stackBounds.width(); int height = stackBounds.height(); @@ -3776,7 +3746,6 @@ class ActivityStack extends Task { proto.write(FILLS_PARENT, matchParentBounds()); getRawBounds().dumpDebug(proto, BOUNDS); - getOverrideDisplayedBounds().dumpDebug(proto, DISPLAYED_BOUNDS); if (mLastNonFullscreenBounds != null) { mLastNonFullscreenBounds.dumpDebug(proto, LAST_NON_FULLSCREEN_BOUNDS); } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 4bcc7e8f5636..fe9e5f3ca09e 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1364,7 +1364,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // still need moveTaskToFrontLocked() below for any transition settings. } if (stack.shouldResizeStackWithLaunchBounds()) { - stack.resize(bounds, null /* configBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME); + stack.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME); } else { // WM resizeTask must be done after the task is moved to the correct stack, // because Task's setBounds() also updates dim layer's bounds, but that has diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7a04894523f5..d92f43b6890c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2786,6 +2786,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final int prevMode = task.getWindowingMode(); + if (prevMode == windowingMode) { + // The task is already in split-screen and with correct windowing mode. + return true; + } + moveTaskToSplitScreenPrimaryTask(task, toTop); return prevMode != task.getWindowingMode(); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2a676e1de5af..a47cdc66fbd8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -26,6 +26,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -141,6 +142,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.WindowConfiguration; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; @@ -3370,34 +3372,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + private boolean isImeControlledByApp() { + return mInputMethodTarget != null && !WindowConfiguration.isSplitScreenWindowingMode( + mInputMethodTarget.getWindowingMode()); + } + boolean isImeAttachedToApp() { - return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null + return isImeControlledByApp() + && mInputMethodTarget.mActivityRecord != null && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // An activity with override bounds should be letterboxed inside its parent bounds, // so it doesn't fill the screen. - && mInputMethodTarget.mActivityRecord.matchParentBounds()); - } - - /** - * Get IME target that should host IME when this display that is reparented to another - * WindowState. - * IME is never displayed in a child display. - * Use {@link WindowState#getImeControlTarget()} when IME target window - * which originally called - * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} is known. - * - * @return {@link WindowState} of host that controls IME. - * {@code null} when {@param dc} is not a virtual display. - * @see DisplayContent#reparent - */ - @Nullable - WindowState getImeControlTarget() { - WindowState imeTarget = mInputMethodTarget; - if (imeTarget != null) { - return imeTarget.getImeControlTarget(); - } - - return getInsetsStateController().getImeSourceProvider().getControlTarget().getWindow(); + && mInputMethodTarget.mActivityRecord.matchParentBounds(); } /** @@ -3407,7 +3393,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * * @param target current IME target. * @return {@link WindowState} that can host IME. - * @see DisplayContent#getImeControlTarget() */ WindowState getImeHostOrFallback(WindowState target) { if (target != null && target.getDisplayContent().canShowIme()) { @@ -3440,7 +3425,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodTarget = target; mInputMethodTargetWaitingAnim = targetWaitingAnim; - assignWindowLayers(false /* setLayoutNeeded */); + assignWindowLayers(true /* setLayoutNeeded */); updateImeParent(); updateImeControlTarget(); } @@ -3448,8 +3433,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** * The IME input target is the window which receives input from IME. It is also a candidate * which controls the visibility and animation of the input method window. - * - * @param target the window that receives input from IME. */ void setInputMethodInputTarget(WindowState target) { if (mInputMethodInputTarget != target) { @@ -3459,12 +3442,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void updateImeControlTarget() { - if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) { - mInputMethodControlTarget = mRemoteInsetsControlTarget; - } else { - // Otherwise, we just use the ime input target - mInputMethodControlTarget = mInputMethodInputTarget; - } + mInputMethodControlTarget = computeImeControlTarget(); mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget); } @@ -3477,6 +3455,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } /** + * Computes the window where we hand IME control to. + */ + @VisibleForTesting + InsetsControlTarget computeImeControlTarget() { + if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) { + return mRemoteInsetsControlTarget; + } else { + // Otherwise, we just use the ime target as received from IME. + return mInputMethodInputTarget; + } + } + + /** * Computes the window the IME should be attached to. */ @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index cb0d8536fe72..1ca82ceeb570 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -266,7 +266,7 @@ class InsetsSourceProvider { if (getSource().getType() == ITYPE_IME) { setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); } - final Transaction t = mDisplayContent.getPendingTransaction(); + final Transaction t = mDisplayContent.mWmService.mTransactionFactory.get(); mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */, ANIMATION_TYPE_INSETS_CONTROL, null /* animationFinishedCallback */); final SurfaceControl leash = mAdapter.mCapturedLeash; @@ -281,6 +281,9 @@ class InsetsSourceProvider { t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber); t.deferTransactionUntil(leash, barrier, frameNumber); } + // Applying the transaction here can prevent the client from applying its transaction sooner + // than us which makes us overwrite the client's operation to the leash. + t.apply(); mControlTarget = target; mControl = new InsetsSourceControl(mSource.getType(), leash, new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top)); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index ba14d48d38ea..4ac319ddf6ce 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -171,7 +171,7 @@ class InsetsStateController { if (aboveIme) { state = new InsetsState(state); - state.removeSource(ITYPE_IME); + state.setSourceVisible(ITYPE_IME, false); } return state; diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 0a9878dd660b..51053b2e7123 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -165,17 +165,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity"); - // TODO(multi-display) currently only support recents animation in default display. - final DisplayContent dc = - mService.mRootWindowContainer.getDefaultDisplay().mDisplayContent; - if (!mWindowManager.canStartRecentsAnimation()) { - notifyAnimationCancelBeforeStart(recentsAnimationRunner); - ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, - "Can't start recents animation, nextAppTransition=%s", - dc.mAppTransition.getAppTransition()); - return; - } - // If the activity is associated with the recents stack, then try and get that first ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 54210ae1c0b0..2ce10a74c949 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -898,11 +898,11 @@ public class RecentsAnimationController implements DeathRecipient { TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) { mTask = task; mIsRecentTaskInvisible = isRecentTaskInvisible; - mBounds.set(mTask.getDisplayedBounds()); + mBounds.set(mTask.getBounds()); mLocalBounds.set(mBounds); Point tmpPos = new Point(); - mTask.getRelativeDisplayedPosition(tmpPos); + mTask.getRelativePosition(tmpPos); mLocalBounds.offsetTo(tmpPos.x, tmpPos.y); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ad1a205a4910..ea5967a51c5f 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -52,7 +52,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; -import static android.content.res.Configuration.EMPTY; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; @@ -353,10 +352,6 @@ class Task extends WindowContainer<WindowContainer> { final Rect mPreparedFrozenBounds = new Rect(); final Configuration mPreparedFrozenMergedConfig = new Configuration(); - // If non-empty, bounds used to display the task during animations/interactions. - // TODO(b/119687367): This member is temporary. - private final Rect mOverrideDisplayedBounds = new Rect(); - // Id of the previous display the stack was on. int mPrevDisplayId = INVALID_DISPLAY; @@ -1378,6 +1373,9 @@ class Task extends WindowContainer<WindowContainer> { void addChild(WindowContainer child, int index) { // If this task had any child before we added this one. boolean hadChild = hasChild(); + // getActivityType() looks at the top child, so we need to read the type before adding + // a new child in case the new child is on top and UNDEFINED. + final int activityType = getActivityType(); index = getAdjustedChildPosition(child, index); super.addChild(child, index); @@ -1418,7 +1416,7 @@ class Task extends WindowContainer<WindowContainer> { ActivityTaskManager.getMaxAppRecentsLimitStatic()); } else { // Otherwise make all added activities match this one. - r.setActivityType(getActivityType()); + r.setActivityType(activityType); } updateEffectiveIntent(); @@ -2795,29 +2793,6 @@ class Task extends WindowContainer<WindowContainer> { } } - /** - * Displayed bounds are used to set where the task is drawn at any given time. This is - * separate from its actual bounds so that the app doesn't see any meaningful configuration - * changes during transitionary periods. - */ - void setOverrideDisplayedBounds(Rect overrideDisplayedBounds) { - if (overrideDisplayedBounds != null) { - adjustForMinimalTaskDimensions(overrideDisplayedBounds, mOverrideDisplayedBounds); - mOverrideDisplayedBounds.set(overrideDisplayedBounds); - } else { - mOverrideDisplayedBounds.setEmpty(); - } - updateSurfacePosition(); - } - - /** - * Gets the bounds that override where the task is displayed. See - * {@link android.app.IActivityTaskManager#resizeDockedStack} why this is needed. - */ - Rect getOverrideDisplayedBounds() { - return mOverrideDisplayedBounds; - } - boolean isResizeable(boolean checkSupportsPip) { return (mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode) || (checkSupportsPip && mSupportsPictureInPicture)); @@ -2851,49 +2826,6 @@ class Task extends WindowContainer<WindowContainer> { mPreparedFrozenMergedConfig.setTo(getConfiguration()); } - /** - * Align the task to the adjusted bounds. - * - * @param adjustedBounds Adjusted bounds to which the task should be aligned. - * @param tempInsetBounds Insets bounds for the task. - * @param alignBottom True if the task's bottom should be aligned to the adjusted - * bounds's bottom; false if the task's top should be aligned - * the adjusted bounds's top. - */ - void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) { - if (!isResizeable() || EMPTY.equals(getRequestedOverrideConfiguration())) { - return; - } - - getBounds(mTmpRect2); - if (alignBottom) { - int offsetY = adjustedBounds.bottom - mTmpRect2.bottom; - mTmpRect2.offset(0, offsetY); - } else { - mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top); - } - if (tempInsetBounds == null || tempInsetBounds.isEmpty()) { - setOverrideDisplayedBounds(null); - setBounds(mTmpRect2); - } else { - setOverrideDisplayedBounds(mTmpRect2); - setBounds(tempInsetBounds); - } - } - - /** - * Gets the current overridden displayed bounds. These will be empty if the task is not - * currently overriding where it is displayed. - */ - @Override - public Rect getDisplayedBounds() { - if (mOverrideDisplayedBounds.isEmpty()) { - return super.getDisplayedBounds(); - } else { - return mOverrideDisplayedBounds; - } - } - @Override void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, Rect outSurfaceInsets) { @@ -3431,7 +3363,6 @@ class Task extends WindowContainer<WindowContainer> { pw.println(prefix + "taskId=" + mTaskId); pw.println(doublePrefix + "mBounds=" + getBounds().toShortString()); pw.println(doublePrefix + "appTokens=" + mChildren); - pw.println(doublePrefix + "mDisplayedBounds=" + mOverrideDisplayedBounds.toShortString()); final String triplePrefix = doublePrefix + " "; final String quadruplePrefix = triplePrefix + " "; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 7219164ad2f1..899ab247077a 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2062,7 +2062,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // TODO: Remove this and use #getBounds() instead once we set an app transition animation // on TaskStack. Rect getAnimationBounds(int appStackClipMode) { - return getDisplayedBounds(); + return getBounds(); } /** @@ -2124,7 +2124,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Separate position and size for use in animators. mTmpRect.set(getAnimationBounds(appStackClipMode)); if (sHierarchicalAnimations) { - getRelativeDisplayedPosition(mTmpPoint); + getRelativePosition(mTmpPoint); } else { mTmpPoint.set(mTmpRect.left, mTmpRect.top); } @@ -2304,14 +2304,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + void resetSurfacePositionForAnimationLeash(Transaction t) { + t.setPosition(mSurfaceControl, 0, 0); + mLastSurfacePosition.set(0, 0); + } + @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { mLastLayer = -1; reassignLayer(t); // Leash is now responsible for position, so set our position to 0. - t.setPosition(mSurfaceControl, 0, 0); - mLastSurfacePosition.set(0, 0); + resetSurfacePositionForAnimationLeash(t); } @Override @@ -2395,7 +2399,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return; } - getRelativeDisplayedPosition(mTmpPos); + getRelativePosition(mTmpPos); if (mTmpPos.equals(mLastSurfacePosition)) { return; } @@ -2410,16 +2414,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** - * Displayed bounds specify where to display this container at. It differs from bounds during - * certain operations (like animation or interactive dragging). - * - * @return the bounds to display this container at. - */ - Rect getDisplayedBounds() { - return getBounds(); - } - - /** * The {@code outFrame} retrieved by this method specifies where the animation will finish * the entrance animation, as the next frame will display the window at these coordinates. In * case of exit animation, this is where the animation will start, as the frame before the @@ -2439,7 +2433,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< outSurfaceInsets.setEmpty(); } - void getRelativeDisplayedPosition(Point outPos) { + void getRelativePosition(Point outPos) { // In addition to updateSurfacePosition, we keep other code that sets // position from fighting with the organizer if (isOrganized()) { @@ -2447,11 +2441,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return; } - final Rect dispBounds = getDisplayedBounds(); + final Rect dispBounds = getBounds(); outPos.set(dispBounds.left, dispBounds.top); final WindowContainer parent = getParent(); if (parent != null) { - final Rect parentBounds = parent.getDisplayedBounds(); + final Rect parentBounds = parent.getBounds(); outPos.offset(-parentBounds.left, -parentBounds.top); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a488af7cdee0..f55a1b3f6ab3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2445,8 +2445,17 @@ public class WindowManagerService extends IWindowManager.Stub if (controls != null) { final int length = Math.min(controls.length, outControls.length); for (int i = 0; i < length; i++) { - outControls[i] = win.isClientLocal() - ? new InsetsSourceControl(controls[i]) : controls[i]; + final InsetsSourceControl control = controls[i]; + + // Check if we are sending invalid leashes. + final SurfaceControl leash = control != null ? control.getLeash() : null; + if (leash != null && !leash.isValid()) { + Slog.wtf(TAG, leash + " is not valid before sending to " + win, + leash.getReleaseStack()); + } + + outControls[i] = win.isClientLocal() && control != null + ? new InsetsSourceControl(control) : control; } } } @@ -2788,18 +2797,6 @@ public class WindowManagerService extends IWindowManager.Stub return mRecentsAnimationController; } - /** - * @return Whether the next recents animation can continue to start. Called from - * {@link RecentsAnimation#startRecentsActivity}. - */ - boolean canStartRecentsAnimation() { - // TODO(multi-display): currently only default display support recent activity - if (getDefaultDisplayContentLocked().mAppTransition.isTransitionSet()) { - return false; - } - return true; - } - void cancelRecentsAnimation( @RecentsAnimationController.ReorderMode int reorderMode, String reason) { if (mRecentsAnimationController != null) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index d9c0219c4779..8b27667475a9 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -320,7 +320,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final ActivityStack stack = (ActivityStack) container; if (stack.inPinnedWindowingMode()) { stack.resize(config.windowConfiguration.getBounds(), - null /* configBounds */, PRESERVE_WINDOWS, true /* deferResume */); + PRESERVE_WINDOWS, true /* deferResume */); } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2e1b907e71bc..00c84ecb9f1f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -999,18 +999,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP frame.inset(left, top, right, bottom); } - @Override - public Rect getDisplayedBounds() { - final Task task = getTask(); - if (task != null) { - Rect bounds = task.getOverrideDisplayedBounds(); - if (!bounds.isEmpty()) { - return bounds; - } - } - return super.getDisplayedBounds(); - } - void computeFrame(DisplayFrames displayFrames) { getLayoutingWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout); computeFrameLw(); @@ -1065,7 +1053,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP layoutXDiff = 0; layoutYDiff = 0; } else { - windowFrames.mContainingFrame.set(getDisplayedBounds()); + windowFrames.mContainingFrame.set(getBounds()); if (mActivityRecord != null && !mActivityRecord.mFrozenBounds.isEmpty()) { // If the bounds are frozen, we still want to translate the window freely and only @@ -1223,7 +1211,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP parentLeft = ((WindowState) parent).mWindowFrames.mFrame.left; parentTop = ((WindowState) parent).mWindowFrames.mFrame.top; } else if (parent != null) { - final Rect parentBounds = parent.getDisplayedBounds(); + final Rect parentBounds = parent.getBounds(); parentLeft = parentBounds.left; parentTop = parentBounds.top; } @@ -1469,7 +1457,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void onDisplayChanged(DisplayContent dc) { - if (dc != null && mDisplayContent != null + if (dc != null && mDisplayContent != null && dc != mDisplayContent && mDisplayContent.mInputMethodInputTarget == this) { dc.setInputMethodInputTarget(mDisplayContent.mInputMethodInputTarget); mDisplayContent.mInputMethodInputTarget = null; @@ -5250,16 +5238,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override - public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { - super.onAnimationLeashCreated(t, leash); - } - - @Override - public void onAnimationLeashLost(Transaction t) { - super.onAnimationLeashLost(t); - } - - @Override @VisibleForTesting void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { @@ -5300,7 +5278,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x, -parent.mWindowFrames.mFrame.top + mTmpPoint.y); } else if (parentWindowContainer != null) { - final Rect parentBounds = parentWindowContainer.getDisplayedBounds(); + final Rect parentBounds = parentWindowContainer.getBounds(); outPoint.offset(-parentBounds.left, -parentBounds.top); } @@ -5352,7 +5330,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // this promotion. final WindowState imeTarget = getDisplayContent().mInputMethodTarget; boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this - && imeTarget.mToken == mToken && imeTarget.compareTo(this) <= 0; + && imeTarget.mToken == mToken + && getParent() != null + && imeTarget.compareTo(this) <= 0; return inTokenWithAndAboveImeTarget; } return false; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index d4470f8334ea..99577077d65d 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1030,7 +1030,7 @@ class WindowStateAnimator { mTmpPos.x = 0; mTmpPos.y = 0; if (stack != null) { - stack.getRelativeDisplayedPosition(mTmpPos); + stack.getRelativePosition(mTmpPos); } xOffset = -mTmpPos.x; diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index b4e770f24eaf..21c76874f5c0 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -632,6 +632,15 @@ class WindowToken extends WindowContainer<WindowState> { } } + @Override + void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { + // Keep the transformed position to animate because the surface will show in different + // rotation than the animator of leash. + if (!isFixedRotationTransforming()) { + super.resetSurfacePositionForAnimationLeash(t); + } + } + /** * Gives a chance to this {@link WindowToken} to adjust the {@link * android.view.WindowManager.LayoutParams} of its windows. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 1da074002456..2c0d4c0c9208 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4733,33 +4733,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!parent && isSeparateProfileChallengeEnabled(userHandle)) { // If this user has a separate challenge, only return its restrictions. return getUserDataUnchecked(userHandle).mAdminList; - } else { - // Return all admins for this user and the profiles that are visible from this - // user that do not use a separate work challenge. - ArrayList<ActiveAdmin> admins = new ArrayList<ActiveAdmin>(); - for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { - DevicePolicyData policy = getUserData(userInfo.id); - if (!userInfo.isManagedProfile()) { - admins.addAll(policy.mAdminList); - } else { - // For managed profiles, we always include the policies set on the parent - // profile. Additionally, we include the ones set on the managed profile - // if no separate challenge is in place. - boolean hasSeparateChallenge = isSeparateProfileChallengeEnabled(userInfo.id); - final int N = policy.mAdminList.size(); - for (int i = 0; i < N; i++) { - ActiveAdmin admin = policy.mAdminList.get(i); - if (admin.hasParentActiveAdmin()) { - admins.add(admin.getParentActiveAdmin()); - } - if (!hasSeparateChallenge) { - admins.add(admin); - } - } - } - } - return admins; } + // Either parent == true, or isSeparateProfileChallengeEnabled == false + // If parent is true, query the parent user of userHandle by definition, + // If isSeparateProfileChallengeEnabled is false, userHandle points to a managed profile + // with unified challenge so also need to query the parent user who owns the credential. + return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), + (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); } /** @@ -4777,6 +4757,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isManagedProfile(userHandle)) { return getUserDataUnchecked(userHandle).mAdminList; } + return getActiveAdminsForUserAndItsManagedProfilesLocked(userHandle, + /* shouldIncludeProfileAdmins */ (user) -> false); + } + + /** + * Returns the list of admins on the given user, as well as parent admins for each managed + * profile associated with the given user. Optionally also include the admin of each managed + * profile. + * <p> Should not be called on a profile user. + */ + @GuardedBy("getLockObject()") + private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesLocked(int userHandle, + Predicate<UserInfo> shouldIncludeProfileAdmins) { ArrayList<ActiveAdmin> admins = new ArrayList<>(); mInjector.binderWithCleanCallingIdentity(() -> { for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { @@ -4784,12 +4777,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (userInfo.id == userHandle) { admins.addAll(policy.mAdminList); } else if (userInfo.isManagedProfile()) { - // For managed profiles, policies set on the parent profile will be included for (int i = 0; i < policy.mAdminList.size(); i++) { ActiveAdmin admin = policy.mAdminList.get(i); if (admin.hasParentActiveAdmin()) { admins.add(admin.getParentActiveAdmin()); } + if (shouldIncludeProfileAdmins.test(userInfo)) { + admins.add(admin); + } } } else { Slog.w(LOG_TAG, "Unknown user type: " + userInfo); @@ -5366,6 +5361,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + @Override + public boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser) { + if (!mHasFeature) { + return true; + } + enforceFullCrossUsersPermission(userHandle); + enforceNotManagedProfile(userHandle, "check password sufficiency"); + enforceUserUnlocked(userHandle); + + synchronized (getLockObject()) { + PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(userHandle); + + // Combine password policies across the user and its profiles. Profile admins are + // included if the profile is to be unified or currently has unified challenge + List<ActiveAdmin> admins = getActiveAdminsForUserAndItsManagedProfilesLocked(userHandle, + /* shouldIncludeProfileAdmins */ (user) -> user.id == profileUser + || !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); + ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>(admins.size()); + for (ActiveAdmin admin : admins) { + adminMetrics.add(admin.mPasswordPolicy.getMinMetrics()); + } + return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics), + PASSWORD_COMPLEXITY_NONE, false, metrics).isEmpty(); + } + } + private boolean isActivePasswordSufficientForUserLocked( boolean passwordValidAtLastCheckpoint, @Nullable PasswordMetrics metrics, int userHandle, boolean parent) { diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 149dfa6be6c4..c1f237f91b44 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -258,10 +258,6 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v mountExistingImages(); } -FileId IncrementalService::idFromMetadata(std::span<const uint8_t> metadata) { - return IncFs_FileIdFromMetadata({(const char*)metadata.data(), metadata.size()}); -} - IncrementalService::~IncrementalService() { { std::lock_guard lock(mJobMutex); @@ -1016,7 +1012,7 @@ bool IncrementalService::startLoading(StorageId storage) const { return false; } } - return dataLoaderStub->start(); + return dataLoaderStub->requestStart(); } void IncrementalService::mountExistingImages() { @@ -1337,13 +1333,13 @@ void IncrementalService::extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle std::vector<IncFsDataBlock> instructions(numBlocks); auto remainingData = std::span(libData.get(), entry.uncompressed_length); for (int i = 0; i < numBlocks; i++) { - const auto blockSize = std::min<uint16_t>(constants().blockSize, remainingData.size()); + const auto blockSize = std::min<long>(constants().blockSize, remainingData.size()); instructions[i] = IncFsDataBlock{ .fileFd = writeFd.get(), .pageIndex = static_cast<IncFsBlockIndex>(i), .compression = INCFS_COMPRESSION_KIND_NONE, .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = blockSize, + .dataSize = static_cast<uint32_t>(blockSize), .data = reinterpret_cast<const char*>(remainingData.data()), }; remainingData = remainingData.subspan(blockSize); @@ -1475,12 +1471,15 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { } IncrementalService::DataLoaderStub::~DataLoaderStub() { - CHECK(mStatus == -1 || mStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) - << "Dataloader has to be destroyed prior to destructor: " << mId - << ", status: " << mStatus; + waitForDestroy(); } bool IncrementalService::DataLoaderStub::create() { + { + std::unique_lock lock(mStatusMutex); + mStartRequested = false; + mDestroyRequested = false; + } bool created = false; auto status = mService.mDataLoaderManager->initializeDataLoader(mId, mParams, mControl, this, &created); @@ -1491,12 +1490,18 @@ bool IncrementalService::DataLoaderStub::create() { return true; } -bool IncrementalService::DataLoaderStub::start() { - if (mStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) { +bool IncrementalService::DataLoaderStub::requestStart() { + { + std::unique_lock lock(mStatusMutex); mStartRequested = true; - return true; + if (mStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) { + return true; + } } + return start(); +} +bool IncrementalService::DataLoaderStub::start() { sp<IDataLoader> dataloader; auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader); if (!status.isOk()) { @@ -1513,8 +1518,21 @@ bool IncrementalService::DataLoaderStub::start() { } void IncrementalService::DataLoaderStub::destroy() { - mDestroyRequested = true; + { + std::unique_lock lock(mStatusMutex); + mDestroyRequested = true; + } mService.mDataLoaderManager->destroyDataLoader(mId); + + waitForDestroy(); +} + +bool IncrementalService::DataLoaderStub::waitForDestroy() { + auto now = std::chrono::steady_clock::now(); + std::unique_lock lock(mStatusMutex); + return mStatusCondition.wait_until(lock, now + 60s, [this] { + return mStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED; + }); } binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) { @@ -1523,34 +1541,36 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount } if (mListener) { - // Give an external listener a chance to act before we destroy something. mListener->onStatusChanged(mountId, newStatus); } + bool startRequested; + bool destroyRequested; { - std::unique_lock l(mService.mLock); - const auto& ifs = mService.getIfsLocked(mountId); - if (!ifs) { - LOG(WARNING) << "Received data loader status " << int(newStatus) - << " for unknown mount " << mountId; + std::unique_lock lock(mStatusMutex); + if (mStatus == newStatus) { return binder::Status::ok(); } - mStatus = newStatus; - if (!mDestroyRequested && newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) { - mService.deleteStorageLocked(*ifs, std::move(l)); - return binder::Status::ok(); - } + startRequested = mStartRequested; + destroyRequested = mDestroyRequested; + + mStatus = newStatus; } switch (newStatus) { case IDataLoaderStatusListener::DATA_LOADER_CREATED: { - if (mStartRequested) { + if (startRequested) { + LOG(WARNING) << "Start was requested, triggering, for mount: " << mountId; start(); } break; } case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: { + if (!destroyRequested) { + LOG(WARNING) << "DataLoader destroyed, reconnecting, for mount: " << mountId; + create(); + } break; } case IDataLoaderStatusListener::DATA_LOADER_STARTED: { @@ -1589,4 +1609,8 @@ binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams return binder::Status::ok(); } +FileId IncrementalService::idFromMetadata(std::span<const uint8_t> metadata) { + return IncFs_FileIdFromMetadata({(const char*)metadata.data(), metadata.size()}); +} + } // namespace android::incremental diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index c016bab067be..8c79d7725dcf 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -180,27 +180,32 @@ private: ~DataLoaderStub(); bool create(); - bool start(); + bool requestStart(); void destroy(); // accessors MountId id() const { return mId; } const DataLoaderParamsParcel& params() const { return mParams; } - int status() const { return mStatus.load(); } + int status() const { return mStatus; } bool startRequested() const { return mStartRequested; } private: binder::Status onStatusChanged(MountId mount, int newStatus) final; + bool start(); + bool waitForDestroy(); + IncrementalService& mService; MountId const mId; DataLoaderParamsParcel const mParams; FileSystemControlParcel const mControl; DataLoaderStatusListener const mListener; - std::atomic<int> mStatus = -1; + std::mutex mStatusMutex; + std::condition_variable mStatusCondition; + int mStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED; bool mStartRequested = false; - bool mDestroyRequested = false; + bool mDestroyRequested = true; }; using DataLoaderStubPtr = sp<DataLoaderStub>; diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 117dca8c37b3..f9737fee450d 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -103,25 +103,34 @@ private: TemporaryFile logFile; }; -class FakeDataLoader : public IDataLoader { +class MockDataLoader : public IDataLoader { public: - IBinder* onAsBinder() override { return nullptr; } - binder::Status create(int32_t, const DataLoaderParamsParcel&, const FileSystemControlParcel&, - const sp<IDataLoaderStatusListener>&) override { - return binder::Status::ok(); - } - binder::Status start(int32_t) override { return binder::Status::ok(); } - binder::Status stop(int32_t) override { return binder::Status::ok(); } - binder::Status destroy(int32_t) override { return binder::Status::ok(); } - binder::Status prepareImage(int32_t, - const std::vector<InstallationFileParcel>&, - const std::vector<std::string>&) override { - return binder::Status::ok(); + MockDataLoader() { + ON_CALL(*this, create(_, _, _, _)).WillByDefault(Return((binder::Status::ok()))); + ON_CALL(*this, start(_)).WillByDefault(Return((binder::Status::ok()))); + ON_CALL(*this, stop(_)).WillByDefault(Return((binder::Status::ok()))); + ON_CALL(*this, destroy(_)).WillByDefault(Return((binder::Status::ok()))); + ON_CALL(*this, prepareImage(_, _, _)).WillByDefault(Return((binder::Status::ok()))); } + IBinder* onAsBinder() override { return nullptr; } + MOCK_METHOD4(create, + binder::Status(int32_t id, const DataLoaderParamsParcel& params, + const FileSystemControlParcel& control, + const sp<IDataLoaderStatusListener>& listener)); + MOCK_METHOD1(start, binder::Status(int32_t id)); + MOCK_METHOD1(stop, binder::Status(int32_t id)); + MOCK_METHOD1(destroy, binder::Status(int32_t id)); + MOCK_METHOD3(prepareImage, + binder::Status(int32_t id, const std::vector<InstallationFileParcel>& addedFiles, + const std::vector<std::string>& removedFiles)); }; class MockDataLoaderManager : public DataLoaderManagerWrapper { public: + MockDataLoaderManager(sp<IDataLoader> dataLoader) : mDataLoaderHolder(std::move(dataLoader)) { + EXPECT_TRUE(mDataLoaderHolder != nullptr); + } + MOCK_CONST_METHOD5(initializeDataLoader, binder::Status(int32_t mountId, const DataLoaderParamsParcel& params, const FileSystemControlParcel& control, @@ -144,9 +153,9 @@ public: ON_CALL(*this, getDataLoader(_, _)) .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk)); } - void destroyDataLoaderOk() { + void destroyDataLoaderSuccess() { ON_CALL(*this, destroyDataLoader(_)) - .WillByDefault(Invoke(this, &MockDataLoaderManager::setDataLoaderStatusDestroyed)); + .WillByDefault(Invoke(this, &MockDataLoaderManager::destroyDataLoaderOk)); } binder::Status initializeDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params, const FileSystemControlParcel& control, @@ -155,20 +164,30 @@ public: mId = mountId; mListener = listener; mServiceConnector = control.service; + mDataLoader = mDataLoaderHolder; *_aidl_return = true; - return binder::Status::ok(); + return mDataLoader->create(mountId, params, control, listener); } binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) { *_aidl_return = mDataLoader; return binder::Status::ok(); } - void setDataLoaderStatusNotReady() { - mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); - } - void setDataLoaderStatusReady() { + void setDataLoaderStatusCreated() { mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED); } - binder::Status setDataLoaderStatusDestroyed(int32_t id) { + void setDataLoaderStatusStarted() { + mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_STARTED); + } + void setDataLoaderStatusDestroyed() { + mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); + } + binder::Status destroyDataLoaderOk(int32_t id) { + if (mDataLoader) { + if (auto status = mDataLoader->destroy(id); !status.isOk()) { + return status; + } + mDataLoader = nullptr; + } if (mListener) { mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); } @@ -185,7 +204,8 @@ private: int mId; sp<IDataLoaderStatusListener> mListener; sp<IIncrementalServiceConnector> mServiceConnector; - sp<IDataLoader> mDataLoader = sp<IDataLoader>(new FakeDataLoader()); + sp<IDataLoader> mDataLoader; + sp<IDataLoader> mDataLoaderHolder; }; class MockIncFs : public IncFsWrapper { @@ -303,7 +323,9 @@ public: void SetUp() override { auto vold = std::make_unique<NiceMock<MockVoldService>>(); mVold = vold.get(); - auto dataloaderManager = std::make_unique<NiceMock<MockDataLoaderManager>>(); + sp<NiceMock<MockDataLoader>> dataLoader{new NiceMock<MockDataLoader>}; + mDataLoader = dataLoader.get(); + auto dataloaderManager = std::make_unique<NiceMock<MockDataLoaderManager>>(dataLoader); mDataLoaderManager = dataloaderManager.get(); auto incFs = std::make_unique<NiceMock<MockIncFs>>(); mIncFs = incFs.get(); @@ -321,7 +343,7 @@ public: mRootDir.path); mDataLoaderParcel.packageName = "com.test"; mDataLoaderParcel.arguments = "uri"; - mDataLoaderManager->destroyDataLoaderOk(); + mDataLoaderManager->destroyDataLoaderSuccess(); mIncrementalService->onSystemReady(); } @@ -352,6 +374,7 @@ protected: NiceMock<MockDataLoaderManager>* mDataLoaderManager; NiceMock<MockAppOpsManager>* mAppOpsManager; NiceMock<MockJniWrapper>* mJni; + NiceMock<MockDataLoader>* mDataLoader; std::unique_ptr<IncrementalService> mIncrementalService; TemporaryDir mRootDir; DataLoaderParamsParcel mDataLoaderParcel; @@ -410,7 +433,11 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderFails(); + EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoader, start(_)).Times(0); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = @@ -424,46 +451,84 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderSuccess(); + EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoader, start(_)).Times(0); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, IncrementalService::CreateOptions::CreateNew); ASSERT_GE(storageId, 0); + mDataLoaderManager->setDataLoaderStatusCreated(); mIncrementalService->deleteStorage(storageId); } -TEST_F(IncrementalServiceTest, testOnStatusNotReady) { +TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderSuccess(); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + mDataLoaderManager->getDataLoaderSuccess(); + EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(2); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2); + EXPECT_CALL(*mDataLoader, start(_)).Times(0); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, IncrementalService::CreateOptions::CreateNew); ASSERT_GE(storageId, 0); - mDataLoaderManager->setDataLoaderStatusNotReady(); + mDataLoaderManager->setDataLoaderStatusCreated(); + // Simulated crash/other connection breakage. + mDataLoaderManager->setDataLoaderStatusDestroyed(); } -TEST_F(IncrementalServiceTest, testStartDataLoaderSuccess) { +TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderSuccess(); mDataLoaderManager->getDataLoaderSuccess(); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoader, start(_)).Times(1); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + mDataLoaderManager->setDataLoaderStatusCreated(); + ASSERT_TRUE(mIncrementalService->startLoading(storageId)); + mDataLoaderManager->setDataLoaderStatusStarted(); +} + +TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { + mVold->mountIncFsSuccess(); + mIncFs->makeFileSuccess(); + mVold->bindMountSuccess(); + mDataLoaderManager->initializeDataLoaderSuccess(); + mDataLoaderManager->getDataLoaderSuccess(); + EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoader, start(_)).Times(1); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, IncrementalService::CreateOptions::CreateNew); ASSERT_GE(storageId, 0); - mDataLoaderManager->setDataLoaderStatusReady(); ASSERT_TRUE(mIncrementalService->startLoading(storageId)); + mDataLoaderManager->setDataLoaderStatusCreated(); } TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e2a247394a81..2c3e3df46e7c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -112,6 +112,7 @@ import com.android.server.inputmethod.InputMethodSystemProperty; import com.android.server.inputmethod.MultiClientInputMethodManagerService; import com.android.server.integrity.AppIntegrityManagerService; import com.android.server.lights.LightsService; +import com.android.server.location.LocationManagerService; import com.android.server.media.MediaResourceMonitorService; import com.android.server.media.MediaRouterService; import com.android.server.media.MediaSessionService; diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 109c1191523b..270a3b50b934 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -78,6 +78,7 @@ <uses-permission android:name="android.permission.DUMP"/> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index 73a191dc78cc..22f8b9c8ae92 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -66,7 +66,7 @@ public class AudioDeviceBrokerTest { mContext = InstrumentationRegistry.getTargetContext(); mMockAudioService = mock(AudioService.class); - mSpyAudioSystem = spy(AudioSystemAdapter.getAlwaysOkAdapter()); + mSpyAudioSystem = spy(AudioSystemAdapter.getConfigurableAdapter()); mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory); mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java new file mode 100644 index 000000000000..6185ae6d93f9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Looper; +import android.os.UserHandle; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class AudioServiceTest { + private static final String TAG = "AudioServiceTest"; + + private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100; + + private Context mContext; + private AudioSystemAdapter mAudioSystem; + @Spy private SystemServerAdapter mSpySystemServer; + // the class being unit-tested here + private AudioService mAudioService; + + private static boolean sLooperPrepared = false; + + @Before + public void setUp() throws Exception { + if (!sLooperPrepared) { + Looper.prepare(); + sLooperPrepared = true; + } + mContext = InstrumentationRegistry.getTargetContext(); + mAudioSystem = AudioSystemAdapter.getConfigurableAdapter(); + mSpySystemServer = spy(SystemServerAdapter.getNoOpAdapter()); + mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer); + } + + /** + * Test muting the mic reports the expected value, and the corresponding intent was fired + * @throws Exception + */ + @Test + public void testMuteMicrophone() throws Exception { + Log.i(TAG, "running testMuteMicrophone"); + Assert.assertNotNull(mAudioService); + final AudioSystemAdapter.AudioSystemConfigurableAdapter testAudioSystem = + (AudioSystemAdapter.AudioSystemConfigurableAdapter) mAudioSystem; + testAudioSystem.configureMuteMicrophoneToFail(false); + for (boolean muted : new boolean[] { true, false}) { + testAudioSystem.configureIsMicrophoneMuted(!muted); + mAudioService.setMicrophoneMute(muted, mContext.getOpPackageName(), + UserHandle.getCallingUserId()); + Assert.assertEquals("mic mute reporting wrong value", + muted, mAudioService.isMicrophoneMuted()); + // verify the intent for mic mute changed is supposed to be fired + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); + verify(mSpySystemServer, times(1)) + .sendMicrophoneMuteChangedIntent(); + reset(mSpySystemServer); + } + } + + /** + * Test muting the mic with simulated failure reports the expected value, and the corresponding + * intent was fired + * @throws Exception + */ + @Test + public void testMuteMicrophoneWhenFail() throws Exception { + Log.i(TAG, "running testMuteMicrophoneWhenFail"); + Assert.assertNotNull(mAudioService); + final AudioSystemAdapter.AudioSystemConfigurableAdapter testAudioSystem = + (AudioSystemAdapter.AudioSystemConfigurableAdapter) mAudioSystem; + testAudioSystem.configureMuteMicrophoneToFail(true); + for (boolean muted : new boolean[] { true, false}) { + testAudioSystem.configureIsMicrophoneMuted(!muted); + mAudioService.setMicrophoneMute(muted, mContext.getOpPackageName(), + UserHandle.getCallingUserId()); + Assert.assertEquals("mic mute reporting wrong value", + !muted, mAudioService.isMicrophoneMuted()); + // verify the intent for mic mute changed is supposed to be fired + Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); + verify(mSpySystemServer, times(1)) + .sendMicrophoneMuteChangedIntent(); + reset(mSpySystemServer); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index fe224ce058f4..09d1d3a270ba 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -1986,29 +1986,32 @@ public class DevicePolicyManagerTest extends DpmTestBase { Sets.newSet( UserManager.DISALLOW_CONFIG_DATE_TIME, UserManager.DISALLOW_ADD_USER, - UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_BLUETOOTH_SHARING, - UserManager.DISALLOW_CONFIG_BLUETOOTH, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, - UserManager.DISALLOW_CONFIG_LOCATION, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, UserManager.DISALLOW_CONFIG_PRIVATE_DNS, UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_CONFIG_WIFI, - UserManager.DISALLOW_CONTENT_CAPTURE, - UserManager.DISALLOW_CONTENT_SUGGESTIONS, UserManager.DISALLOW_DATA_ROAMING, - UserManager.DISALLOW_DEBUGGING_FEATURES, UserManager.DISALLOW_SAFE_BOOT, - UserManager.DISALLOW_SHARE_LOCATION, UserManager.DISALLOW_SMS, UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_AIRPLANE_MODE, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, - UserManager.DISALLOW_OUTGOING_CALLS, UserManager.DISALLOW_UNMUTE_MICROPHONE ); + private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS = + Sets.newSet( + UserManager.DISALLOW_CONFIG_BLUETOOTH, + UserManager.DISALLOW_CONFIG_LOCATION, + UserManager.DISALLOW_CONFIG_WIFI, + UserManager.DISALLOW_CONTENT_CAPTURE, + UserManager.DISALLOW_CONTENT_SUGGESTIONS, + UserManager.DISALLOW_DEBUGGING_FEATURES, + UserManager.DISALLOW_SHARE_LOCATION, + UserManager.DISALLOW_OUTGOING_CALLS + ); + public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID); @@ -2021,7 +2024,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) { - addAndRemoveUserRestrictionOnParentDpm(restriction); + addAndRemoveGlobalUserRestrictionOnParentDpm(restriction); + } + for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS) { + addAndRemoveLocalUserRestrictionOnParentDpm(restriction); } parentDpm.setCameraDisabled(admin1, true); @@ -2047,7 +2053,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { reset(getServices().userManagerInternal); } - private void addAndRemoveUserRestrictionOnParentDpm(String restriction) { + private void addAndRemoveGlobalUserRestrictionOnParentDpm(String restriction) { parentDpm.addUserRestriction(admin1, restriction); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(DpmMockContext.CALLER_USER_HANDLE), @@ -2063,6 +2069,22 @@ public class DevicePolicyManagerTest extends DpmTestBase { ); } + private void addAndRemoveLocalUserRestrictionOnParentDpm(String restriction) { + parentDpm.addUserRestriction(admin1, restriction); + verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( + eq(DpmMockContext.CALLER_USER_HANDLE), + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM, restriction), + eq(false)); + parentDpm.clearUserRestriction(admin1, restriction); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .getParentActiveAdmin() + .getEffectiveRestrictions() + ); + } + public void testNoDefaultEnabledUserRestrictions() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); @@ -4841,6 +4863,33 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertFalse(dpm.isActivePasswordSufficient()); } + public void testIsPasswordSufficientAfterProfileUnification() throws Exception { + final int managedProfileUserId = DpmMockContext.CALLER_USER_HANDLE; + final int managedProfileAdminUid = + UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); + mContext.binder.callingUid = managedProfileAdminUid; + + addManagedProfile(admin1, managedProfileAdminUid, admin1); + doReturn(true).when(getServices().lockPatternUtils) + .isSeparateProfileChallengeEnabled(managedProfileUserId); + + dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC); + parentDpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC); + + when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM)) + .thenReturn(computeForPassword("1234".getBytes())); + + // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly + // on the parent admin) + assertTrue(dpm.isPasswordSufficientAfterProfileUnification(UserHandle.USER_SYSTEM, + UserHandle.USER_NULL)); + // Numeric password is not compliant if profile is to be unified: the profile has a + // QUALITY_ALPHABETIC policy on itself which will be enforced on the password after + // unification. + assertFalse(dpm.isPasswordSufficientAfterProfileUnification(UserHandle.USER_SYSTEM, + managedProfileUserId)); + } + private void setActivePasswordState(PasswordMetrics passwordMetrics) throws Exception { final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 07d7830c9b0f..12b144f2b778 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -209,6 +209,26 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + public void testManagedProfileChallengeUnification_parentUserNoPassword() throws Exception { + // Start with a profile with unified challenge, parent user has not password + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(MANAGED_PROFILE_USER_ID)); + + // Set a separate challenge on the profile + assertTrue(mService.setLockCredential( + newPassword("12345678"), nonePassword(), MANAGED_PROFILE_USER_ID)); + assertNotEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(MANAGED_PROFILE_USER_ID)); + + // Now unify again, profile should become passwordless again + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, + newPassword("12345678")); + assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(MANAGED_PROFILE_USER_ID)); + } + + @Test public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception { assertTrue(mService.setLockCredential( newPassword("password"), diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING new file mode 100644 index 000000000000..8070bd1f06a1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.om." + } + ] + } + ] +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index 62589ebb92fe..22020ad45666 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -54,6 +54,7 @@ public class DexoptOptionsTests { assertFalse(opt.isForce()); assertFalse(opt.isDexoptIdleBackgroundJob()); assertFalse(opt.isDexoptInstallWithDexMetadata()); + assertFalse(opt.isDexoptInstallForRestore()); } @Test @@ -67,7 +68,8 @@ public class DexoptOptionsTests { DexoptOptions.DEXOPT_DOWNGRADE | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB | - DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE; + DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE | + DexoptOptions.DEXOPT_FOR_RESTORE; DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags); assertEquals(mPackageName, opt.getPackageName()); @@ -82,6 +84,7 @@ public class DexoptOptionsTests { assertTrue(opt.isDexoptAsSharedLibrary()); assertTrue(opt.isDexoptIdleBackgroundJob()); assertTrue(opt.isDexoptInstallWithDexMetadata()); + assertTrue(opt.isDexoptInstallForRestore()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index af89761acf9d..939b7a0beb49 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -35,6 +35,7 @@ import android.os.Build; import android.os.Bundle; import android.os.FileUtils; import android.platform.test.annotations.Presubmit; +import android.util.SparseIntArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -542,7 +543,12 @@ public class PackageParserLegacyCoreTest { @Test public void testUsesSdk() throws Exception { - parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x); + ParsedPackage pkg = + parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x); + SparseIntArray minExtVers = pkg.getMinExtensionVersions(); + assertEquals(1, minExtVers.size()); + assertEquals(0, minExtVers.get(10000, -1)); + try { parsePackage("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5, x -> x); fail("Expected parsing exception due to incompatible extension SDK version"); diff --git a/services/tests/servicestests/src/com/android/server/tv/TvRemoteProviderWatcherTest.java b/services/tests/servicestests/src/com/android/server/tv/TvRemoteProviderWatcherTest.java index 0a2bb620eb11..55e526f01aef 100644 --- a/services/tests/servicestests/src/com/android/server/tv/TvRemoteProviderWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/TvRemoteProviderWatcherTest.java @@ -84,6 +84,22 @@ public class TvRemoteProviderWatcherTest { } @Test + public void acceptsValidCsvPackageName() { + // Test intentionally includes empty spacing for a more complex test + when(mMockResources.getString(com.android.internal.R.string.config_tvRemoteServicePackage)) + .thenReturn(",,foo, " + TV_REMOTE_SERVICE_PACKAGE_NAME + ",bar, baz,,"); + assertTrue(mTvRemoteProviderWatcher.verifyServiceTrusted(createTvServiceInfo())); + } + + @Test + public void rejectsInvalidCsvPackageName() { + // Checks include empty strings to validate that processing as well + when(mMockResources.getString(com.android.internal.R.string.config_tvRemoteServicePackage)) + .thenReturn(",,foo,, ,bar, baz,,"); + assertFalse(mTvRemoteProviderWatcher.verifyServiceTrusted(createTvServiceInfo())); + } + + @Test public void tvServiceIsTrusted() { assertTrue(mTvRemoteProviderWatcher.verifyServiceTrusted(createTvServiceInfo())); } diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index fcbd5072ae35..13248a00c1b0 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -96,13 +96,6 @@ public class TunerResourceManagerServiceTest { } }; - private static int getResourceIdFromHandle(int resourceHandle) { - if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) { - return resourceHandle; - } - return (resourceHandle & 0x00ff0000) >> 16; - } - @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -247,7 +240,7 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])) + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); } @@ -275,7 +268,7 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])) + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); } @@ -307,7 +300,8 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(0); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) + .isEqualTo(0); } @Test @@ -344,7 +338,8 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId()); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) + .isEqualTo(infos[0].getId()); request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); @@ -354,7 +349,8 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[1].getId()); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) + .isEqualTo(infos[1].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId()) .isInUse()).isTrue(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].getId()) @@ -464,7 +460,8 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId()); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) + .isEqualTo(infos[0].getId()); request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); @@ -474,7 +471,8 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[1].getId()); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) + .isEqualTo(infos[1].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId()) .isInUse()).isTrue(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId()) @@ -487,6 +485,49 @@ public class TunerResourceManagerServiceTest { } @Test + public void releaseFrontendTest_UnderTheSameExclusiveGroup() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[1]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId = new int[1]; + TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); + mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); + assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + TunerFrontendRequest request = + new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendHandle = new int[1]; + try { + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]); + assertThat(frontendId).isEqualTo(infos[0].getId()); + assertThat(mTunerResourceManagerService + .getFrontendResource(infos[1].getId()).isInUse()).isTrue(); + + // Release frontend + mTunerResourceManagerService.releaseFrontendInternal(frontendId); + assertThat(mTunerResourceManagerService + .getFrontendResource(frontendId).isInUse()).isFalse(); + assertThat(mTunerResourceManagerService + .getFrontendResource(infos[1].getId()).isInUse()).isFalse(); + assertThat(mTunerResourceManagerService + .getClientProfile(clientId[0]).getInUseFrontendIds().size()).isEqualTo(0); + } + + @Test public void unregisterClientTest_usingFrontend() { // Register client ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/, @@ -513,7 +554,8 @@ public class TunerResourceManagerServiceTest { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId()); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) + .isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId()) .isInUse()).isTrue(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId()) @@ -543,7 +585,8 @@ public class TunerResourceManagerServiceTest { TunerDemuxRequest request = new TunerDemuxRequest(clientId[0]); assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle)) .isTrue(); - assertThat(getResourceIdFromHandle(demuxHandle[0])).isEqualTo(0); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle[0])) + .isEqualTo(0); } @Test @@ -560,6 +603,6 @@ public class TunerResourceManagerServiceTest { TunerDescramblerRequest request = new TunerDescramblerRequest(clientId[0]); assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle)) .isTrue(); - assertThat(getResourceIdFromHandle(desHandle[0])).isEqualTo(0); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(desHandle[0])).isEqualTo(0); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java new file mode 100644 index 000000000000..c69ef8d93282 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + +import static android.os.UserHandle.USER_CURRENT; +import static android.os.UserHandle.USER_SYSTEM; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Notification; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ArchiveTest extends UiServiceTestCase { + private static final int SIZE = 5; + + private NotificationManagerService.Archive mArchive; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mArchive = new NotificationManagerService.Archive(SIZE); + mArchive.updateHistoryEnabled(USER_SYSTEM, true); + mArchive.updateHistoryEnabled(USER_CURRENT, true); + } + + private StatusBarNotification getNotification(String pkg, int id, UserHandle user) { + Notification n = new Notification.Builder(getContext(), "test") + .setContentTitle("A") + .setWhen(1205) + .build(); + return new StatusBarNotification( + pkg, pkg, id, null, 0, 0, n, user, null, System.currentTimeMillis()); + } + + + @Test + public void testRecordAndRead() { + List<String> expected = new ArrayList<>(); + for (int i = 0; i < SIZE; i++) { + StatusBarNotification sbn = getNotification("pkg" + i, i, + UserHandle.of(i % 2 ==0 ? USER_SYSTEM : USER_CURRENT)); + expected.add(sbn.getKey()); + mArchive.record(sbn, REASON_CANCEL); + } + + List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(SIZE, true)); + assertThat(actual).hasSize(expected.size()); + for (StatusBarNotification sbn : actual) { + assertThat(expected).contains(sbn.getKey()); + } + } + + @Test + public void testRecordAndRead_overLimit() { + List<String> expected = new ArrayList<>(); + for (int i = 0; i < (SIZE * 2); i++) { + StatusBarNotification sbn = getNotification("pkg" + i, i, UserHandle.of(USER_SYSTEM)); + mArchive.record(sbn, REASON_CANCEL); + if (i >= SIZE) { + expected.add(sbn.getKey()); + } + } + + List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray((SIZE * 2), true)); + assertThat(actual).hasSize(expected.size()); + for (StatusBarNotification sbn : actual) { + assertThat(expected).contains(sbn.getKey()); + } + } + + @Test + public void testDoesNotRecordIfHistoryDisabled() { + mArchive.updateHistoryEnabled(USER_CURRENT, false); + List<String> expected = new ArrayList<>(); + for (int i = 0; i < SIZE; i++) { + StatusBarNotification sbn = getNotification("pkg" + i, i, + UserHandle.of(i % 2 ==0 ? USER_SYSTEM : USER_CURRENT)); + mArchive.record(sbn, REASON_CANCEL); + if (i % 2 ==0) { + expected.add(sbn.getKey()); + } + } + + List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(SIZE, true)); + assertThat(actual).hasSize(expected.size()); + for (StatusBarNotification sbn : actual) { + assertThat(expected).contains(sbn.getKey()); + } + } + + @Test + public void testRemovesEntriesWhenHistoryDisabled() { + mArchive.updateHistoryEnabled(USER_CURRENT, true); + List<String> expected = new ArrayList<>(); + for (int i = 0; i < SIZE; i++) { + StatusBarNotification sbn = getNotification("pkg" + i, i, + UserHandle.of(i % 2 ==0 ? USER_SYSTEM : USER_CURRENT)); + mArchive.record(sbn, REASON_CANCEL); + if (i % 2 ==0) { + expected.add(sbn.getKey()); + } + } + mArchive.updateHistoryEnabled(USER_CURRENT, false); + + List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(SIZE, true)); + assertThat(actual).hasSize(expected.size()); + for (StatusBarNotification sbn : actual) { + assertThat(expected).contains(sbn.getKey()); + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 7b7470cca85a..28ff9a545513 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -77,6 +77,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -96,6 +97,10 @@ public class ManagedServicesTest extends UiServiceTestCase { UserInfo mZero = new UserInfo(0, "zero", 0); UserInfo mTen = new UserInfo(10, "ten", 0); + private String mDefaultsString; + private String mVersionString; + private final Set<ComponentName> mDefaults = new ArraySet(); + private ManagedServices mService; private static final String SETTING = "setting"; private static final String SECONDARY_SETTING = "secondary_setting"; @@ -106,8 +111,8 @@ public class ManagedServicesTest extends UiServiceTestCase { private ArrayMap<Integer, String> mExpectedSecondaryComponentNames; // type : user : list of approved - private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary = new ArrayMap<>(); - private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary = new ArrayMap<>(); + private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary; + private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary; @Before public void setUp() throws Exception { @@ -132,6 +137,9 @@ public class ManagedServicesTest extends UiServiceTestCase { profileIds.add(12); when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); + mVersionString = "2"; + mExpectedPrimary = new ArrayMap<>(); + mExpectedSecondary = new ArrayMap<>(); mExpectedPrimaryPackages = new ArrayMap<>(); mExpectedPrimaryPackages.put(0, "this.is.a.package.name:another.package"); mExpectedPrimaryPackages.put(10, "this.is.another.package"); @@ -155,6 +163,8 @@ public class ManagedServicesTest extends UiServiceTestCase { "this.is.another.package:component:package"); mExpectedSecondary.put(APPROVAL_BY_PACKAGE, mExpectedSecondaryPackages); mExpectedSecondary.put(APPROVAL_BY_COMPONENT, mExpectedSecondaryComponentNames); + mService = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_COMPONENT); } @Test @@ -1178,9 +1188,99 @@ public class ManagedServicesTest extends UiServiceTestCase { } } + @Test + public void loadDefaults_noVersionNoDefaults() throws Exception { + resetComponentsAndPackages(); + loadXml(mService); + assertEquals(mService.getDefaultComponents().size(), 0); + } + + @Test + public void loadDefaults_noVersionNoDefaultsOneActive() throws Exception { + resetComponentsAndPackages(); + mService.addDefaultComponentOrPackage("package/class"); + loadXml(mService); + assertEquals(1, mService.getDefaultComponents().size()); + assertTrue(mService.getDefaultComponents() + .contains(ComponentName.unflattenFromString("package/class"))); + } + + @Test + public void loadDefaults_noVersionWithDefaults() throws Exception { + resetComponentsAndPackages(); + mDefaults.add(new ComponentName("default", "class")); + loadXml(mService); + assertEquals(mService.getDefaultComponents(), mDefaults); + } + + @Test + public void loadDefaults_versionOneWithDefaultsWithActive() throws Exception { + resetComponentsAndPackages(); + mDefaults.add(new ComponentName("default", "class")); + mExpectedPrimaryComponentNames.put(0, "package/class"); + mVersionString = "1"; + loadXml(mService); + assertEquals(mService.getDefaultComponents(), + new ArraySet(Arrays.asList(new ComponentName("package", "class")))); + } + + @Test + public void loadDefaults_versionTwoWithDefaultsWithActive() throws Exception { + resetComponentsAndPackages(); + mDefaults.add(new ComponentName("default", "class")); + mDefaultsString = "default/class"; + mExpectedPrimaryComponentNames.put(0, "package/class"); + mVersionString = "2"; + loadXml(mService); + assertEquals(1, mService.getDefaultComponents().size()); + mDefaults.forEach(pkg -> { + assertTrue(mService.getDefaultComponents().contains(pkg)); + }); + } + + @Test + public void loadDefaults_versionOneWithXMLDefaultsWithActive() throws Exception { + resetComponentsAndPackages(); + mDefaults.add(new ComponentName("default", "class")); + mDefaultsString = "xml/class"; + mExpectedPrimaryComponentNames.put(0, "package/class"); + mVersionString = "1"; + loadXml(mService); + assertEquals(mService.getDefaultComponents(), + new ArraySet(Arrays.asList(new ComponentName("xml", "class")))); + } + + @Test + public void loadDefaults_versionTwoWithXMLDefaultsWithActive() throws Exception { + resetComponentsAndPackages(); + mDefaults.add(new ComponentName("default", "class")); + mDefaultsString = "xml/class"; + mExpectedPrimaryComponentNames.put(0, "package/class"); + mVersionString = "2"; + loadXml(mService); + assertEquals(mService.getDefaultComponents(), + new ArraySet(Arrays.asList(new ComponentName("xml", "class")))); + } + + private void resetComponentsAndPackages() { + ArrayMap<Integer, ArrayMap<Integer, String>> empty = new ArrayMap(1); + ArrayMap<Integer, String> emptyPkgs = new ArrayMap(0); + empty.append(mService.mApprovalLevel, emptyPkgs); + mExpectedPrimary = empty; + mExpectedPrimaryComponentNames = emptyPkgs; + mExpectedPrimaryPackages = emptyPkgs; + mExpectedSecondary = empty; + mExpectedSecondaryComponentNames = emptyPkgs; + mExpectedSecondaryPackages = emptyPkgs; + } + private void loadXml(ManagedServices service) throws Exception { final StringBuffer xml = new StringBuffer(); - xml.append("<" + service.getConfig().xmlTag + ">\n"); + String xmlTag = service.getConfig().xmlTag; + xml.append("<" + xmlTag + + (mDefaultsString != null ? " defaults=\"" + mDefaultsString + "\" " : "") + + (mVersionString != null ? " version=\"" + mVersionString + "\" " : "") + + ">\n"); for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) { xml.append(getXmlEntry( mExpectedPrimary.get(service.mApprovalLevel).get(userId), userId, true)); @@ -1197,7 +1297,7 @@ public class ManagedServicesTest extends UiServiceTestCase { + ManagedServices.ATT_USER_ID + "=\"98\" " + ManagedServices.ATT_IS_PRIMARY + "=\"false\" " + ManagedServices.ATT_APPROVED_LIST + "=\"98\" />\n"); - xml.append("</" + service.getConfig().xmlTag + ">"); + xml.append("</" + xmlTag + ">"); XmlPullParser parser = Xml.newPullParser(); parser.setInput(new BufferedInputStream( @@ -1224,6 +1324,7 @@ public class ManagedServicesTest extends UiServiceTestCase { private void addExpectedServices(final ManagedServices service, final List<String> packages, int userId) { + ManagedServices.Config config = service.getConfig(); when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))). thenAnswer(new Answer<List<ResolveInfo>>() { @Override @@ -1233,7 +1334,7 @@ public class ManagedServicesTest extends UiServiceTestCase { Intent invocationIntent = (Intent) args[0]; if (invocationIntent != null) { if (invocationIntent.getAction().equals( - service.getConfig().serviceInterface) + config.serviceInterface) && packages.contains(invocationIntent.getPackage())) { List<ResolveInfo> dummyServices = new ArrayList<>(); for (int i = 1; i <= 3; i ++) { @@ -1431,6 +1532,11 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Override + protected void loadDefaultsFromConfig() { + mDefaultComponents.addAll(mDefaults); + } + + @Override protected String getRequiredPermission() { return null; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 88186cdb3873..ab4dc476ff20 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -72,13 +72,13 @@ public class NotificationAssistantsTest extends UiServiceTestCase { Object mLock = new Object(); + UserInfo mZero = new UserInfo(0, "zero", 0); UserInfo mTen = new UserInfo(10, "ten", 0); @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - getContext().setMockPackageManager(mPm); getContext().addMockSystemService(Context.USER_SERVICE, mUm); mAssistants = spy(mNm.new NotificationAssistants(getContext(), mLock, mUserProfiles, miPm)); @@ -122,7 +122,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { @Test public void testXmlUpgradeExistingApprovedComponents() throws Exception { - String xml = "<enabled_assistants>" + String xml = "<enabled_assistants version=\"2\" defaults=\"b\\b\">" + "<service_listing approved=\"b/b\" user=\"10\" primary=\"true\" />" + "</enabled_assistants>"; 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 3cd0e92964ec..ecdd9e548e6a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -113,6 +113,7 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; @@ -234,6 +235,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock private LauncherApps mLauncherApps; @Mock + private ShortcutServiceInternal mShortcutServiceInternal; + @Mock ActivityManager mActivityManager; @Mock Resources mResources; @@ -466,6 +469,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mShortcutHelper = mService.getShortcutHelper(); mShortcutHelper.setLauncherApps(mLauncherApps); + mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal); // Set the testable bubble extractor RankingHelper rankingHelper = mService.getRankingHelper(); @@ -782,33 +786,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { userInfos.add(new UserInfo(0, "", 0)); final ArraySet<ComponentName> validAssistants = new ArraySet<>(); validAssistants.add(ComponentName.unflattenFromString(testComponent)); - final String originalComponent = DeviceConfig.getProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE - ); - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE, - testComponent, - false - ); when(mActivityManager.isLowRamDevice()).thenReturn(false); when(mAssistants.queryPackageForServices(isNull(), anyInt(), anyInt())) .thenReturn(validAssistants); - when(mAssistants.getDefaultComponents()).thenReturn(new ArraySet<>()); + when(mAssistants.getDefaultComponents()).thenReturn(validAssistants); when(mUm.getEnabledProfiles(anyInt())).thenReturn(userInfos); mService.setDefaultAssistantForUser(userId); verify(mAssistants).setPackageOrComponentEnabled( eq(testComponent), eq(userId), eq(true), eq(true)); - - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE, - originalComponent, - false - ); } @Test @@ -6088,8 +6075,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { List<ShortcutInfo> shortcutInfos = new ArrayList<>(); ShortcutInfo info = mock(ShortcutInfo.class); when(info.isLongLived()).thenReturn(true); + when(info.isEnabled()).thenReturn(true); shortcutInfos.add(info); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); // Test: Send the bubble notification mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), @@ -6148,8 +6138,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { List<ShortcutInfo> shortcutInfos = new ArrayList<>(); ShortcutInfo info = mock(ShortcutInfo.class); when(info.isLongLived()).thenReturn(true); + when(info.isEnabled()).thenReturn(true); shortcutInfos.add(info); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); // Test: Send the bubble notification mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), @@ -6289,7 +6282,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.loadDefaultApprovedServices(USER_SYSTEM); - verify(mConditionProviders, times(1)).addDefaultComponentOrPackage("test"); + verify(mConditionProviders, times(1)).loadDefaultsFromConfig(); } // TODO: add tests for the rest of the non-empty cases @@ -6492,7 +6485,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ShortcutInfo si = mock(ShortcutInfo.class); when(si.getShortLabel()).thenReturn("Hello"); when(si.isLongLived()).thenReturn(true); + when(si.isEnabled()).thenReturn(true); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si)); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); List<ConversationChannelWrapper> conversations = mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 427237c4be0f..ac51750f23f8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -15,6 +15,9 @@ */ package com.android.server.notification; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; @@ -50,6 +53,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; @@ -132,6 +136,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Spy IContentProvider mTestIContentProvider = new MockIContentProvider(); @Mock Context mContext; @Mock ZenModeHelper mMockZenModeHelper; + @Mock AppOpsManager mAppOpsManager; private NotificationManager.Policy mTestNotificationPolicy; @@ -187,7 +192,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), + anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() @@ -1464,7 +1472,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any()); resetZenModeHelper(); @@ -1475,7 +1484,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, never()).setNotificationPolicy(any()); resetZenModeHelper(); @@ -2241,7 +2251,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -2253,7 +2264,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setHideSilentStatusIcons(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -2349,7 +2361,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2360,7 +2373,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2372,7 +2386,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.revokeNotificationDelegate(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2384,7 +2399,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.toggleNotificationDelegate(PKG_O, UID_O, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); // appears disabled @@ -2402,7 +2418,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.revokeNotificationDelegate(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); // appears disabled @@ -2417,14 +2434,71 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testBubblePreference_defaults() throws Exception { - assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); + assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); - assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); + assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + } + + @Test + public void testBubblePreference_upgradeWithSAWPermission() throws Exception { + when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), + anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n" + + "<channel id=\"someId\" name=\"hi\"" + + " importance=\"3\"/>" + + "</package>" + + "</ranking>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + } + + @Test + public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception { + when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), + anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n" + + "<channel id=\"someId\" name=\"hi\"" + + " importance=\"3\"/>" + + "</package>" + + "</ranking>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O)); assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + + mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_SELECTED); + assertEquals(BUBBLE_PREFERENCE_SELECTED, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE, + mHelper.getAppLockedFields(PKG_O, UID_O)); + + ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); + loadStreamXml(baos, false, UserHandle.USER_ALL); + + assertEquals(BUBBLE_PREFERENCE_SELECTED, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE, + mHelper.getAppLockedFields(PKG_O, UID_O)); } @Test @@ -2435,7 +2509,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getAppLockedFields(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); @@ -2949,7 +3024,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -2969,7 +3045,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testPlaceholderConversationId_shortcutRequired() throws Exception { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -2989,7 +3066,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNormalConversationId_shortcutRequired() throws Exception { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -3009,7 +3087,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNoConversationId_shortcutRequired() throws Exception { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, + mAppOpsManager); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java index 50fb9b425652..f7304bd0075b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java @@ -16,7 +16,11 @@ package com.android.server.notification; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -25,6 +29,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutServiceInternal; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; @@ -58,6 +63,8 @@ public class ShortcutHelperTest extends UiServiceTestCase { @Mock ShortcutHelper.ShortcutListener mShortcutListener; @Mock + ShortcutServiceInternal mShortcutServiceInternal; + @Mock NotificationRecord mNr; @Mock Notification mNotif; @@ -72,7 +79,8 @@ public class ShortcutHelperTest extends UiServiceTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mShortcutHelper = new ShortcutHelper(mLauncherApps, mShortcutListener); + mShortcutHelper = new ShortcutHelper( + mLauncherApps, mShortcutListener, mShortcutServiceInternal); when(mNr.getKey()).thenReturn(KEY); when(mNr.getSbn()).thenReturn(mSbn); when(mSbn.getPackageName()).thenReturn(PKG); @@ -138,4 +146,80 @@ public class ShortcutHelperTest extends UiServiceTestCase { callback.onShortcutsChanged(PKG, shortcutInfos, mock(UserHandle.class)); verify(mShortcutListener).onShortcutRemoved(mNr.getKey()); } + + @Test + public void testGetValidShortcutInfo_noMatchingShortcut() { + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); + + assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isNull(); + } + + @Test + public void testGetValidShortcutInfo_nullShortcut() { + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + shortcuts.add(null); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); + + assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isNull(); + } + + @Test + public void testGetValidShortcutInfo_notLongLived() { + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.isLongLived()).thenReturn(false); + when(si.isEnabled()).thenReturn(true); + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + shortcuts.add(si); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); + + assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isNull(); + } + + @Test + public void testGetValidShortcutInfo_notSharingShortcut() { + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.isLongLived()).thenReturn(true); + when(si.isEnabled()).thenReturn(true); + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + shortcuts.add(si); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(false); + + assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isNull(); + } + + @Test + public void testGetValidShortcutInfo_notEnabled() { + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.isLongLived()).thenReturn(true); + when(si.isEnabled()).thenReturn(false); + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + shortcuts.add(si); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); + + assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isNull(); + } + + @Test + public void testGetValidShortcutInfo_isValid() { + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.isLongLived()).thenReturn(true); + when(si.isEnabled()).thenReturn(true); + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + shortcuts.add(si); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); + + assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isSameAs(si); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index bc66fa7ff48d..5bbb4d9e712c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1324,7 +1324,7 @@ public class ActivityRecordTests extends ActivityTestsBase { display.rotateInDifferentOrientationIfNeeded(mActivity); display.mFixedRotationLaunchingApp = mActivity; - displayRotation.updateRotationUnchecked(false /* forceUpdate */); + displayRotation.updateRotationUnchecked(true /* forceUpdate */); assertTrue(displayRotation.isRotatingSeamlessly()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 4532400e3b34..17dd26ed18e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -89,13 +89,13 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { public void testOnPictureInPictureRequested() throws RemoteException { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); - ClientLifecycleManager lifecycleManager = mService.getLifecycleManager(); - doNothing().when(lifecycleManager).scheduleTransaction(any()); + final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class); + doReturn(mockLifecycleManager).when(mService).getLifecycleManager(); doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); mService.requestPictureInPictureMode(activity.token); - verify(lifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture()); + verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture()); final ClientTransaction transaction = mClientTransactionCaptor.getValue(); // Check that only an enter pip request item callback was scheduled. assertEquals(1, transaction.getCallbacks().size()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 3f47b8722a39..daff14992e94 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -65,6 +65,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @@ -84,12 +85,16 @@ import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.view.DisplayCutout; import android.view.Gravity; +import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowRotationCallback; import android.view.IDisplayWindowRotationController; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; +import android.view.InsetsSourceControl; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.Surface; +import android.view.SurfaceControl.Transaction; import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.test.InsetsModeSession; @@ -809,25 +814,19 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_app() throws Exception { - try (final InsetsModeSession session = - new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_IME)) { - final DisplayContent dc = createNewDisplay(); - dc.mInputMethodTarget = createWindow(null, TYPE_BASE_APPLICATION, "app"); - assertEquals(dc.mInputMethodTarget.mActivityRecord.getSurfaceControl(), - dc.computeImeParent()); - } + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodTarget = createWindow(null, TYPE_BASE_APPLICATION, "app"); + assertEquals(dc.mInputMethodTarget.mActivityRecord.getSurfaceControl(), + dc.computeImeParent()); } @Test public void testComputeImeParent_app_notFullscreen() throws Exception { - try (final InsetsModeSession session = - new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_IME)) { - final DisplayContent dc = createNewDisplay(); - dc.mInputMethodTarget = createWindow(null, TYPE_STATUS_BAR, "app"); - dc.mInputMethodTarget.setWindowingMode( - WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); - } + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodTarget = createWindow(null, TYPE_STATUS_BAR, "app"); + dc.mInputMethodTarget.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); } @Test @@ -842,12 +841,61 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_noApp() throws Exception { - try (final InsetsModeSession session = - new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_IME)) { - final DisplayContent dc = createNewDisplay(); - dc.mInputMethodTarget = createWindow(null, TYPE_STATUS_BAR, "statusBar"); - assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); - } + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodTarget = createWindow(null, TYPE_STATUS_BAR, "statusBar"); + assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); + } + + @Test + public void testComputeImeControlTarget() throws Exception { + final DisplayContent dc = createNewDisplay(); + dc.setRemoteInsetsController(createDisplayWindowInsetsController()); + dc.mInputMethodInputTarget = createWindow(null, TYPE_BASE_APPLICATION, "app"); + dc.mInputMethodTarget = dc.mInputMethodInputTarget; + assertEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget()); + } + + @Test + public void testComputeImeControlTarget_splitscreen() throws Exception { + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodInputTarget = createWindow(null, TYPE_BASE_APPLICATION, "app"); + dc.mInputMethodInputTarget.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + dc.mInputMethodTarget = dc.mInputMethodInputTarget; + dc.setRemoteInsetsController(createDisplayWindowInsetsController()); + assertNotEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget()); + } + + @Test + public void testComputeImeControlTarget_notMatchParentBounds() throws Exception { + spyOn(mAppWindow.mActivityRecord); + doReturn(false).when(mAppWindow.mActivityRecord).matchParentBounds(); + mDisplayContent.mInputMethodInputTarget = mAppWindow; + mDisplayContent.mInputMethodTarget = mDisplayContent.mInputMethodInputTarget; + mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController()); + assertEquals(mAppWindow, mDisplayContent.computeImeControlTarget()); + } + + private IDisplayWindowInsetsController createDisplayWindowInsetsController() { + return new IDisplayWindowInsetsController.Stub() { + + @Override + public void insetsChanged(InsetsState insetsState) throws RemoteException { + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] insetsSourceControls) throws RemoteException { + } + + @Override + public void showInsets(int i, boolean b) throws RemoteException { + } + + @Override + public void hideInsets(int i, boolean b) throws RemoteException { + } + }; } @Test @@ -1040,6 +1088,12 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(config90.orientation, app.getConfiguration().orientation); assertEquals(config90.windowConfiguration.getBounds(), app.getBounds()); + // Make wallaper laid out with the fixed rotation transform. + final WindowToken wallpaperToken = mWallpaperWindow.mToken; + wallpaperToken.linkFixedRotationTransform(app); + mWallpaperWindow.mLayoutNeeded = true; + performLayout(mDisplayContent); + // Force the negative offset to verify it can be updated. mWallpaperWindow.mWinAnimator.mXOffset = mWallpaperWindow.mWinAnimator.mYOffset = -1; assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow, @@ -1047,6 +1101,13 @@ public class DisplayContentTests extends WindowTestsBase { assertThat(mWallpaperWindow.mWinAnimator.mXOffset).isGreaterThan(-1); assertThat(mWallpaperWindow.mWinAnimator.mYOffset).isGreaterThan(-1); + // The wallpaper need to animate with transformed position, so its surface position should + // not be reset. + final Transaction t = wallpaperToken.getPendingTransaction(); + spyOn(t); + mWallpaperWindow.mToken.onAnimationLeashCreated(t, null /* leash */); + verify(t, never()).setPosition(any(), eq(0), eq(0)); + mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token); // The animation in old rotation should be cancelled. diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 61b74b0c1d0f..9f28f45a05d0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -29,9 +29,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.spy; @@ -44,7 +46,6 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.test.InsetsModeSession; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.AfterClass; @@ -153,22 +154,24 @@ public class InsetsStateControllerTest extends WindowTestsBase { @Test public void testStripForDispatch_belowIme() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mBehindIme = true; - assertNotNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME)); + getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); + assertTrue(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible()); } @Test public void testStripForDispatch_aboveIme() { - final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mBehindIme = false; - assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME)); + getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); + assertFalse(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible()); } @Test @@ -188,11 +191,11 @@ public class InsetsStateControllerTest extends WindowTestsBase { // Adding FLAG_NOT_FOCUSABLE makes app above IME. app.mAttrs.flags |= FLAG_NOT_FOCUSABLE; mDisplayContent.computeImeTarget(true); - mDisplayContent.setLayoutNeeded(); mDisplayContent.applySurfaceChangesTransaction(); - // app won't get IME insets while above IME. - assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME)); + // app won't get visible IME insets while above IME even when IME is visible. + getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); + assertFalse(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible()); // Reset invocation counter. clearInvocations(app); @@ -200,49 +203,49 @@ public class InsetsStateControllerTest extends WindowTestsBase { // Removing FLAG_NOT_FOCUSABLE makes app below IME. app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; mDisplayContent.computeImeTarget(true); - mDisplayContent.setLayoutNeeded(); mDisplayContent.applySurfaceChangesTransaction(); // Make sure app got notified. verify(app, atLeast(1)).notifyInsetsChanged(); - // app will get IME insets while below IME. - assertNotNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME)); + // app will get visible IME insets while below IME when IME is visible. + getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); + assertTrue(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible()); } @Test public void testStripForDispatch_childWindow_altFocusable() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; - final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); - - // IME cannot be the IME target. - ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); + mDisplayContent.computeImeTarget(true); + mDisplayContent.setLayoutNeeded(); + mDisplayContent.applySurfaceChangesTransaction(); - assertNull(getController().getInsetsForDispatch(child).peekSource(ITYPE_IME)); + getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); + assertTrue(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible()); + assertFalse(getController().getInsetsForDispatch(child).getSource(ITYPE_IME).isVisible()); } @Test public void testStripForDispatch_childWindow_splitScreen() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); child.mAttrs.flags |= FLAG_NOT_FOCUSABLE; child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); - - // IME cannot be the IME target. - ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - - getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); + mDisplayContent.computeImeTarget(true); + mDisplayContent.setLayoutNeeded(); + mDisplayContent.applySurfaceChangesTransaction(); - assertNull(getController().getInsetsForDispatch(child).peekSource(ITYPE_IME)); + getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); + assertTrue(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible()); + assertFalse(getController().getInsetsForDispatch(child).getSource(ITYPE_IME).isVisible()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 1f6ba7adf114..44ca2cdcb142 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -79,7 +79,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { mService.mWindowManager.setRecentsAnimationController(mRecentsAnimationController); doNothing().when(mService.mWindowManager).initializeRecentsAnimation( anyInt(), any(), any(), anyInt(), any(), any()); - doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation(); final RecentTasks recentTasks = mService.getRecentTasks(); spyOn(recentTasks); diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java index ecb80156aecc..5e8de8792cd1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java @@ -25,6 +25,8 @@ import static android.view.Gravity.BOTTOM; import static android.view.Gravity.LEFT; import static android.view.Gravity.RIGHT; import static android.view.Gravity.TOP; +import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -60,7 +62,6 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.widget.TextView; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.After; @@ -78,8 +79,6 @@ import java.util.function.BooleanSupplier; */ // TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags. // TODO: Test non-Activity windows. -@FlakyTest(detail = "TODO (b/145242835): Re-enable once type mapping is implemented for " - + "PRIVATE_FLAG_IS_SCREEN_DECOR") @SmallTest @Presubmit public class ScreenDecorWindowTests { @@ -187,13 +186,24 @@ public class ScreenDecorWindowTests { assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); } + @Test + public void testProvidesInsetsTypes() { + int[] providesInsetsTypes = new int[]{ITYPE_STATUS_BAR}; + final View win = createWindow("StatusBarSubPanel", TOP, MATCH_PARENT, mDecorThickness, RED, + FLAG_LAYOUT_IN_SCREEN, 0, providesInsetsTypes); + + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + } + private View createDecorWindow(int gravity, int width, int height) { + int[] providesInsetsTypes = + new int[]{gravity == TOP ? ITYPE_STATUS_BAR : ITYPE_NAVIGATION_BAR}; return createWindow("decorWindow", gravity, width, height, RED, - FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR); + FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR, providesInsetsTypes); } private View createWindow(String name, int gravity, int width, int height, int color, int flags, - int privateFlags) { + int privateFlags, int[] providesInsetsTypes) { final View[] viewHolder = new View[1]; final int finalFlag = flags @@ -205,6 +215,7 @@ public class ScreenDecorWindowTests { width, height, TYPE_APPLICATION_OVERLAY, finalFlag, PixelFormat.OPAQUE); lp.gravity = gravity; lp.privateFlags |= privateFlags; + lp.providesInsetsTypes = providesInsetsTypes; final TextView view = new TextView(mContext); view.setText("ScreenDecorWindowTests - " + name); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index 413ae134fe18..7cb5e84e4e48 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -208,4 +208,22 @@ public class TaskStackTests extends WindowTestsBase { assertEquals(stackBounds.left - stackOutset, stack.getLastSurfacePosition().x); assertEquals(stackBounds.top - stackOutset, stack.getLastSurfacePosition().y); } + + @Test + public void testActivityAndTaskGetsProperType() { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack, 0 /* userId */); + ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(mDisplayContent); + + // First activity should become standard + task1.addChild(activity1, 0); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity1.getActivityType()); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType()); + + // Second activity should also become standard + ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(mDisplayContent); + task1.addChild(activity2, WindowContainer.POSITION_TOP); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType()); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index ec77be85aebf..473c1c57d625 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -145,9 +145,5 @@ public class TaskTests extends WindowTestsBase { Rect bounds = new Rect(10, 10, 100, 200); task.setBounds(bounds); assertEquals(new Point(bounds.left, bounds.top), task.getLastSurfacePosition()); - - Rect dispBounds = new Rect(20, 30, 110, 220); - task.setOverrideDisplayedBounds(dispBounds); - assertEquals(new Point(dispBounds.left, dispBounds.top), task.getLastSurfacePosition()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java index 7be05a39cbde..eb2aa41192c2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -310,27 +310,6 @@ public class WindowFrameTests extends WindowTestsBase { assertContentFrame(w, new Rect(resolvedTaskBounds.left, resolvedTaskBounds.top, resolvedTaskBounds.right - contentInsetRight, resolvedTaskBounds.bottom - contentInsetBottom)); - - pf.set(0, 0, logicalWidth, logicalHeight); - // If we set displayed bounds, the insets will be computed with the main task bounds - // but the frame will be positioned according to the displayed bounds. - final int insetLeft = logicalWidth / 5; - final int insetTop = logicalHeight / 5; - final int insetRight = insetLeft + (resolvedTaskBounds.right - resolvedTaskBounds.left); - final int insetBottom = insetTop + (resolvedTaskBounds.bottom - resolvedTaskBounds.top); - task.setOverrideDisplayedBounds(resolvedTaskBounds); - task.setBounds(insetLeft, insetTop, insetRight, insetBottom); - windowFrames.setFrames(pf, pf, cf, cf, pf, cf); - w.computeFrameLw(); - assertEquals(resolvedTaskBounds, w.getFrameLw()); - assertEquals(0, w.getRelativeFrameLw().left); - assertEquals(0, w.getRelativeFrameLw().top); - contentInsetRight = insetRight - cfRight; - contentInsetBottom = insetBottom - cfBottom; - assertContentInset(w, 0, 0, contentInsetRight, contentInsetBottom); - assertContentFrame(w, new Rect(resolvedTaskBounds.left, resolvedTaskBounds.top, - resolvedTaskBounds.right - contentInsetRight, - resolvedTaskBounds.bottom - contentInsetBottom)); } @Test @@ -460,33 +439,6 @@ public class WindowFrameTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 130388666) - public void testDisplayCutout_tempDisplayedBounds() { - // Regular fullscreen task and window - WindowState w = createWindow(); - final Task task = w.getTask(); - task.setBounds(new Rect(0, 0, 1000, 2000)); - task.setOverrideDisplayedBounds(new Rect(0, -500, 1000, 1500)); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final Rect pf = new Rect(0, -500, 1000, 1500); - // Create a display cutout of size 50x50, aligned top-center - final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(500, 0, 550, 50, BOUNDS_POSITION_TOP), - pf.width(), pf.height()); - - final WindowFrames windowFrames = w.getWindowFrames(); - windowFrames.setFrames(pf, pf, pf, pf, pf, pf); - windowFrames.setDisplayCutout(cutout); - w.computeFrameLw(); - - assertEquals(w.getWmDisplayCutout().getDisplayCutout().getSafeInsetTop(), 50); - assertEquals(w.getWmDisplayCutout().getDisplayCutout().getSafeInsetBottom(), 0); - assertEquals(w.getWmDisplayCutout().getDisplayCutout().getSafeInsetLeft(), 0); - assertEquals(w.getWmDisplayCutout().getDisplayCutout().getSafeInsetRight(), 0); - } - - @Test public void testFreeformContentInsets() { removeGlobalMinSizeRestriction(); // fullscreen task doesn't use bounds for computeFrame diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 71b35b62366e..65fb2c0451d8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -657,4 +657,16 @@ public class WindowStateTests extends WindowTestsBase { win0.mActivityRecord.getStack().setFocusable(false); assertTrue(win0.cantReceiveTouchInput()); } + + @Test + public void testNeedsRelativeLayeringToIme_notAttached() { + WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken, + "SameTokenWindow"); + mDisplayContent.mInputMethodTarget = mAppWindow; + sameTokenWindow.mActivityRecord.getStack().setWindowingMode( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + assertTrue(sameTokenWindow.needsRelativeLayeringToIme()); + sameTokenWindow.removeImmediately(); + assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); + } } diff --git a/startop/iorap/functional_tests/Android.bp b/startop/iorap/functional_tests/Android.bp index ad85f1430bdf..8a5bd34af653 100644 --- a/startop/iorap/functional_tests/Android.bp +++ b/startop/iorap/functional_tests/Android.bp @@ -15,7 +15,7 @@ android_test { name: "iorap-functional-tests", srcs: ["src/**/*.java"], - data: ["test_data/*"], + data: [":iorap-functional-test-apps"], static_libs: [ // Non-test dependencies // library under test diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java index c35dd3b783b1..5352be6f283f 100644 --- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java +++ b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java @@ -397,7 +397,7 @@ public class IorapWorkFlowTest { public LogcatTimestamp() throws Exception{ long currentTimeMillis = System.currentTimeMillis(); epochTime = String.format( - "%d.%d", currentTimeMillis/1000, currentTimeMillis%1000); + "%d.%03d", currentTimeMillis/1000, currentTimeMillis%1000); Log.i(TAG, "Current logcat timestamp is " + epochTime); } diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk deleted file mode 120000 index 1c1a437f6a55..000000000000 --- a/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk +++ /dev/null @@ -1 +0,0 @@ -../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v1.apk
\ No newline at end of file diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk deleted file mode 120000 index 7cd41c48ba3a..000000000000 --- a/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk +++ /dev/null @@ -1 +0,0 @@ -../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v2.apk
\ No newline at end of file diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk deleted file mode 120000 index 7f4e996e57d0..000000000000 --- a/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk +++ /dev/null @@ -1 +0,0 @@ -../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v3.apk
\ No newline at end of file diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index 4e14fd3d59a1..d9605522b3b0 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -106,6 +106,7 @@ public abstract class Conference extends Conferenceable { private int mCallerDisplayNamePresentation; private int mCallDirection; private boolean mRingbackRequested = false; + private boolean mIsMultiparty = true; private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { @Override @@ -998,8 +999,8 @@ public abstract class Conference extends Conferenceable { public void onExtrasChanged(Bundle extras) {} /** - * Set whether Telecom should treat this {@link Conference} as a conference call or if it - * should treat it as a single-party call. + * Set whether Telecom should treat this {@link Conference} as a multiparty conference call or + * if it should treat it as a single-party call. * This method is used as part of a workaround regarding IMS conference calls and user * expectation. In IMS, once a conference is formed, the UE is connected to an IMS conference * server. If all participants of the conference drop out of the conference except for one, the @@ -1020,6 +1021,7 @@ public abstract class Conference extends Conferenceable { @TestApi @RequiresPermission(MODIFY_PHONE_STATE) public void setConferenceState(boolean isConference) { + mIsMultiparty = isConference; for (Listener l : mListeners) { l.onConferenceStateChanged(this, isConference); } @@ -1043,6 +1045,20 @@ public abstract class Conference extends Conferenceable { } } + /** + * Determines if the {@link Conference} is considered "multiparty" or not. By default all + * conferences are considered multiparty. A multiparty conference is one where there are + * multiple conference participants (other than the host) in the conference. + * This is tied to {@link #setConferenceState(boolean)}, which is used for some use cases to + * have a conference appear as if it is a standalone call, in which case the conference will + * no longer be multiparty. + * @return {@code true} if conference is treated as a conference (i.e. it is multiparty), + * {@code false} if it should emulate a standalone call (i.e. not multiparty). + * @hide + */ + public boolean isMultiparty() { + return mIsMultiparty; + } /** * Sets the address of this {@link Conference}. Used when {@link #setConferenceState(boolean)} diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 73296986d82e..1b60e4820ad0 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -2505,6 +2505,11 @@ public abstract class ConnectionService extends Service { mAdapter.addConferenceCall(id, parcelableConference); mAdapter.setVideoProvider(id, conference.getVideoProvider()); mAdapter.setVideoState(id, conference.getVideoState()); + // In some instances a conference can start its life as a standalone call with just a + // single participant; ensure we signal to Telecom in this case. + if (!conference.isMultiparty()) { + mAdapter.setConferenceState(id, conference.isMultiparty()); + } // Go through any child calls and set the parent. for (Connection connection : conference.getConnections()) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 545c8a35058f..d2de19aaf89a 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -505,6 +505,15 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; /** + * Flag specifying whether to show an alert dialog for 5G disable when the user disables VoLTE. + * By default this value is {@code false}. + * + * @hide + */ + public static final String KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL = + "volte_5g_limited_alert_dialog_bool"; + + /** * Flag specifying whether the carrier wants to notify the user when a VT call has been handed * over from WIFI to LTE. * <p> @@ -3084,6 +3093,16 @@ public class CarrierConfigManager { public static final String KEY_UNMETERED_NR_NSA_SUB6_BOOL = "unmetered_nr_nsa_sub6_bool"; /** + * Whether NR (non-standalone) should be unmetered when the device is roaming. + * If false, then the values for {@link #KEY_UNMETERED_NR_NSA_BOOL}, + * {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL}, {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL}, + * and unmetered {@link SubscriptionPlan} will be ignored. + * @hide + */ + public static final String KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL = + "unmetered_nr_nsa_when_roaming_bool"; + + /** * Whether NR (standalone) should be unmetered for all frequencies. * If either {@link #KEY_UNMETERED_NR_SA_MMWAVE_BOOL} or * {@link #KEY_UNMETERED_NR_SA_SUB6_BOOL} are true, then this value will be ignored. @@ -3703,6 +3722,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL, false); sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_MERGING_RTT_CALLS_BOOL, false); sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL, false); @@ -4134,6 +4154,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false); sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false); sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false); + sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL, false); sDefaults.putBoolean(KEY_UNMETERED_NR_SA_BOOL, false); sDefaults.putBoolean(KEY_UNMETERED_NR_SA_MMWAVE_BOOL, false); sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f623649fb25e..835ef59f9ef3 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13250,4 +13250,21 @@ public class TelephonyManager { public static void enableServiceHandleCaching() { sServiceHandleCacheEnabled = true; } + + /** + * Whether device can connect to 5G network when two SIMs are active. + * @hide + * TODO b/153669716: remove or make system API. + */ + public boolean canConnectTo5GInDsdsMode() { + ITelephony telephony = getITelephony(); + if (telephony == null) return true; + try { + return telephony.canConnectTo5GInDsdsMode(); + } catch (RemoteException ex) { + return true; + } catch (NullPointerException ex) { + return true; + } + } } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index a116c07e2646..242c2e979571 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -80,7 +80,6 @@ public final class DataCallResponse implements Parcelable { private final int mMtu; private final int mMtuV4; private final int mMtuV6; - private final int mVersion; /** * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error. @@ -126,9 +125,7 @@ public final class DataCallResponse implements Parcelable { ? new ArrayList<>() : new ArrayList<>(gatewayAddresses); mPcscfAddresses = (pcscfAddresses == null) ? new ArrayList<>() : new ArrayList<>(pcscfAddresses); - mMtu = mtu; - mMtuV4 = mMtuV6 = 0; - mVersion = 0; + mMtu = mMtuV4 = mMtuV6 = mtu; } /** @hide */ @@ -136,7 +133,7 @@ public final class DataCallResponse implements Parcelable { @LinkStatus int linkStatus, @ProtocolType int protocolType, @Nullable String interfaceName, @Nullable List<LinkAddress> addresses, @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses, - @Nullable List<InetAddress> pcscfAddresses, int mtuV4, int mtuV6, int version) { + @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6) { mCause = cause; mSuggestedRetryTime = suggestedRetryTime; mId = id; @@ -151,10 +148,9 @@ public final class DataCallResponse implements Parcelable { ? new ArrayList<>() : new ArrayList<>(gatewayAddresses); mPcscfAddresses = (pcscfAddresses == null) ? new ArrayList<>() : new ArrayList<>(pcscfAddresses); - mMtu = 0; + mMtu = mtu; mMtuV4 = mtuV4; mMtuV6 = mtuV6; - mVersion = version; } /** @hide */ @@ -177,7 +173,6 @@ public final class DataCallResponse implements Parcelable { mMtu = source.readInt(); mMtuV4 = source.readInt(); mMtuV6 = source.readInt(); - mVersion = source.readInt(); } /** @@ -247,7 +242,7 @@ public final class DataCallResponse implements Parcelable { */ @Deprecated public int getMtu() { - return mVersion < 5 ? mMtu : 0; + return mMtu; } /** @@ -256,7 +251,7 @@ public final class DataCallResponse implements Parcelable { * Zero or negative values means network has either not sent a value or sent an invalid value. */ public int getMtuV4() { - return mVersion < 5 ? 0 : mMtuV4; + return mMtuV4; } /** @@ -264,7 +259,7 @@ public final class DataCallResponse implements Parcelable { * Zero or negative values means network has either not sent a value or sent an invalid value. */ public int getMtuV6() { - return mVersion < 5 ? 0 : mMtuV6; + return mMtuV6; } @NonNull @@ -282,10 +277,9 @@ public final class DataCallResponse implements Parcelable { .append(" dnses=").append(mDnsAddresses) .append(" gateways=").append(mGatewayAddresses) .append(" pcscf=").append(mPcscfAddresses) - .append(" mtu=").append(mMtu) - .append(" mtuV4=").append(mMtuV4) - .append(" mtuV6=").append(mMtuV6) - .append(" version=").append(mVersion) + .append(" mtu=").append(getMtu()) + .append(" mtuV4=").append(getMtuV4()) + .append(" mtuV6=").append(getMtuV6()) .append("}"); return sb.toString(); } @@ -315,15 +309,14 @@ public final class DataCallResponse implements Parcelable { && mPcscfAddresses.containsAll(other.mPcscfAddresses) && mMtu == other.mMtu && mMtuV4 == other.mMtuV4 - && mMtuV6 == other.mMtuV6 - && mVersion == other.mVersion; + && mMtuV6 == other.mMtuV6; } @Override public int hashCode() { return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, - mMtu, mMtuV4, mMtuV6, mVersion); + mMtu, mMtuV4, mMtuV6); } @Override @@ -346,7 +339,6 @@ public final class DataCallResponse implements Parcelable { dest.writeInt(mMtu); dest.writeInt(mMtuV4); dest.writeInt(mMtuV6); - dest.writeInt(mVersion); } public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR = @@ -403,8 +395,6 @@ public final class DataCallResponse implements Parcelable { private int mMtuV6; - private int mVersion; - /** * Default constructor for Builder. */ @@ -563,29 +553,14 @@ public final class DataCallResponse implements Parcelable { } /** - * Set the IRadio version for this DataCallResponse - * @hide - */ - public @NonNull Builder setVersion(int version) { - mVersion = version; - return this; - } - - /** * Build the DataCallResponse. * * @return the DataCallResponse object. */ public @NonNull DataCallResponse build() { - if (mVersion >= 5) { - return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus, - mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, - mPcscfAddresses, mMtuV4, mMtuV6, mVersion); - } else { - return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus, - mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, - mPcscfAddresses, mMtu); - } + return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus, + mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, + mPcscfAddresses, mMtu, mMtuV4, mMtuV6); } } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 43aeb19fe1bd..f5cd68f050a4 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2268,4 +2268,9 @@ interface ITelephony { * @return operatorinfo on success */ String getManualNetworkSelectionPlmn(int subId); + + /** + * Whether device can connect to 5G network when two SIMs are active. + */ + boolean canConnectTo5GInDsdsMode(); } diff --git a/tests/AppLaunch/Android.bp b/tests/AppLaunch/Android.bp index f90f26f00e6d..75db55122553 100644 --- a/tests/AppLaunch/Android.bp +++ b/tests/AppLaunch/Android.bp @@ -8,6 +8,8 @@ android_test { "android.test.base", "android.test.runner", ], - static_libs: ["androidx.test.rules"], + static_libs: [ + "androidx.test.rules", + "ub-uiautomator"], test_suites: ["device-tests"], } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 2d2f4dbdf907..7d750b7bf690 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -15,6 +15,8 @@ */ package com.android.tests.applaunch; +import static org.junit.Assert.assertNotNull; + import android.accounts.Account; import android.accounts.AccountManager; import android.app.ActivityManager; @@ -29,7 +31,9 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; +import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.util.Log; @@ -46,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.nio.file.Paths; import java.time.format.DateTimeFormatter; import java.time.ZonedDateTime; import java.time.ZoneOffset; @@ -67,6 +72,7 @@ import java.util.Set; * in the following format: * -e apps <app name>^<result key>|<app name>^<result key> */ +@Deprecated public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; @@ -94,6 +100,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; + private static final String ENABLE_SCREEN_RECORDING = "enable_screen_recording"; + private static final int MAX_RECORDING_PARTS = 5; + private static final long VIDEO_TAIL_BUFFER = 500; private static final String SIMPLEPERF_APP_CMD = "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; @@ -144,14 +153,17 @@ public class AppLaunch extends InstrumentationTestCase { private Map<String, Intent> mNameToIntent; private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); + private RecordingThread mCurrentThread; private Map<String, String> mNameToResultKey; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private IActivityManager mAm; + private File launchSubDir = null; private String mSimplePerfCmd = null; private String mLaunchOrder = null; private boolean mDropCache = false; private int mLaunchIterations = 10; private boolean mForceStopApp = true; + private boolean mEnableRecording = false; private int mTraceLaunchCount = 0; private String mTraceDirectoryStr = null; private Bundle mResult = new Bundle(); @@ -166,6 +178,7 @@ public class AppLaunch extends InstrumentationTestCase { private boolean mCycleCleanUp = false; private boolean mTraceAll = false; private boolean mIterationCycle = false; + private UiDevice mDevice; enum IorapStatus { UNDEFINED, @@ -222,7 +235,7 @@ public class AppLaunch extends InstrumentationTestCase { } try { - File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { throw new IOException("Unable to create the lauch file sub directory " @@ -923,9 +936,16 @@ public class AppLaunch extends InstrumentationTestCase { mLaunchIterations = Integer.parseInt(launchIterations); } String forceStopApp = args.getString(KEY_FORCE_STOP_APP); + if (forceStopApp != null) { mForceStopApp = Boolean.parseBoolean(forceStopApp); } + + String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); + + if (enableRecording != null) { + mEnableRecording = Boolean.parseBoolean(enableRecording); + } String appList = args.getString(KEY_APPS); if (appList == null) return; @@ -1038,6 +1058,9 @@ public class AppLaunch extends InstrumentationTestCase { private AppLaunchResult startApp(String appName, String launchReason) throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); + if(mEnableRecording) { + startRecording(appName, launchReason); + } Intent startIntent = mNameToIntent.get(appName); if (startIntent == null) { @@ -1053,6 +1076,10 @@ public class AppLaunch extends InstrumentationTestCase { } catch (InterruptedException e) { // ignore } + + if(mEnableRecording) { + stopRecording(); + } return runnable.getResult(); } @@ -1360,4 +1387,126 @@ public class AppLaunch extends InstrumentationTestCase { } } + + /** + * Start the screen recording while launching the app. + * + * @param appName + * @param launchReason + */ + private void startRecording(String appName, String launchReason) { + Log.v(TAG, "Started Recording"); + mCurrentThread = new RecordingThread("test-screen-record", + String.format("%s_%s", appName, launchReason)); + mCurrentThread.start(); + } + + /** + * Stop already started screen recording. + */ + private void stopRecording() { + // Skip if not directory. + if (launchSubDir == null) { + return; + } + + // Add some extra time to the video end. + SystemClock.sleep(VIDEO_TAIL_BUFFER); + // Ctrl + C all screen record processes. + mCurrentThread.cancel(); + // Wait for the thread to completely die. + try { + mCurrentThread.join(); + } catch (InterruptedException ex) { + Log.e(TAG, "Interrupted when joining the recording thread.", ex); + } + Log.v(TAG, "Stopped Recording"); + } + + /** Returns the recording's name for part {@code part} of launch description. */ + private File getOutputFile(String description, int part) { + // Omit the iteration number for the first iteration. + final String fileName = + String.format( + "%s-video%s.mp4", description, part == 1 ? "" : part); + return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); + } + + + /** + * Encapsulates the start and stop screen recording logic. + * Copied from ScreenRecordCollector. + */ + private class RecordingThread extends Thread { + private final String mDescription; + private final List<File> mRecordings; + + private boolean mContinue; + + public RecordingThread(String name, String description) { + super(name); + + mContinue = true; + mRecordings = new ArrayList<>(); + + assertNotNull("No test description provided for recording.", description); + mDescription = description; + } + + @Override + public void run() { + try { + // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. + for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { + File output = getOutputFile(mDescription, i); + Log.d( + TAG, + String.format("Recording screen to %s", output.getAbsolutePath())); + mRecordings.add(output); + // Make sure not to block on this background command in the main thread so + // that the test continues to run, but block in this thread so it does not + // trigger a new screen recording session before the prior one completes. + getDevice().executeShellCommand( + String.format("screenrecord %s", output.getAbsolutePath())); + } + } catch (IOException e) { + throw new RuntimeException("Caught exception while screen recording."); + } + } + + public void cancel() { + mContinue = false; + + // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. + try { + String[] pids = getDevice().executeShellCommand( + "pidof screenrecord").split(" "); + for (String pid : pids) { + // Avoid empty process ids, because of weird splitting behavior. + if (pid.isEmpty()) { + continue; + } + + getDevice().executeShellCommand( + String.format("kill -2 %s", pid)); + Log.d( + TAG, + String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); + } + } catch (IOException e) { + throw new RuntimeException("Failed to kill screen recording process."); + } + } + + public List<File> getRecordings() { + return mRecordings; + } + } + + public UiDevice getDevice() { + if (mDevice == null) { + mDevice = UiDevice.getInstance(getInstrumentation()); + } + return mDevice; + } } diff --git a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java index f4f610b1b280..fa292bd0d57a 100644 --- a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java +++ b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java @@ -100,7 +100,6 @@ public class DozeTestDream extends DreamService { public void onAttachedToWindow() { super.onAttachedToWindow(); setInteractive(false); - setLowProfile(true); setFullscreen(true); setContentView(R.layout.dream); setScreenBright(false); diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml index c6b4a54f3b0b..dd255ef5233b 100644 --- a/tests/UiBench/AndroidManifest.xml +++ b/tests/UiBench/AndroidManifest.xml @@ -306,5 +306,14 @@ <category android:name="com.android.test.uibench.TEST" /> </intent-filter> </activity> + + <activity + android:name="WindowInsetsControllerActivity" + android:label="WindowInsetsControllerActivity" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.android.test.uibench.TEST" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/UiBench/src/com/android/test/uibench/WindowInsetsControllerActivity.java b/tests/UiBench/src/com/android/test/uibench/WindowInsetsControllerActivity.java new file mode 100644 index 000000000000..e4b89cdd5c8d --- /dev/null +++ b/tests/UiBench/src/com/android/test/uibench/WindowInsetsControllerActivity.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.uibench; + +import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; + +import android.os.Bundle; +import android.os.PersistableBundle; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.List; + +public class WindowInsetsControllerActivity extends AppCompatActivity { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EditText text = new EditText(this); + text.setText("WindowInsetsController"); + setContentView(text); + getWindow().setDecorFitsSystemWindows(false); + + text.setWindowInsetsAnimationCallback( + new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + @NonNull + @Override + public WindowInsets onProgress(@NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + return WindowInsets.CONSUMED; + } + }); + } +} diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 6e6331312eac..a1bb0d586916 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -60,14 +60,13 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.AlarmManager; import android.app.usage.NetworkStatsManager; import android.content.Context; @@ -95,8 +94,6 @@ import android.os.Message; import android.os.Messenger; import android.os.PowerManager; import android.os.SimpleClock; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; import android.telephony.TelephonyManager; import androidx.test.InstrumentationRegistry; @@ -109,7 +106,7 @@ import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; import com.android.testutils.HandlerUtilsKt; -import com.android.testutils.TestableNetworkStatsProvider; +import com.android.testutils.TestableNetworkStatsProviderBinder; import libcore.io.IoUtils; @@ -126,6 +123,7 @@ import java.io.File; import java.time.Clock; import java.time.ZoneOffset; import java.util.Objects; +import java.util.concurrent.Executor; /** * Tests for {@link NetworkStatsService}. @@ -168,14 +166,13 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private @Mock NetworkStatsSettings mSettings; private @Mock IBinder mBinder; private @Mock AlarmManager mAlarmManager; - private @Mock TelephonyManager mTelephonyManager; + @Mock + private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; private HandlerThread mHandlerThread; private NetworkStatsService mService; private INetworkStatsSession mSession; private INetworkManagementEventObserver mNetworkObserver; - @Nullable - private PhoneStateListener mPhoneStateListener; private final Clock mClock = new SimpleClock(ZoneOffset.UTC) { @Override @@ -203,8 +200,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mHandlerThread = new HandlerThread("HandlerThread"); final NetworkStatsService.Dependencies deps = makeDependencies(); mService = new NetworkStatsService(mServiceContext, mNetManager, mAlarmManager, wakeLock, - mClock, mTelephonyManager, mSettings, - mStatsFactory, new NetworkStatsObservers(), mStatsDir, getBaseDir(mStatsDir), deps); + mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir, + getBaseDir(mStatsDir), deps); mElapsedRealtime = 0L; @@ -224,12 +221,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { ArgumentCaptor.forClass(INetworkManagementEventObserver.class); verify(mNetManager).registerObserver(networkObserver.capture()); mNetworkObserver = networkObserver.getValue(); - - // Capture the phone state listener that created by service. - final ArgumentCaptor<PhoneStateListener> phoneStateListenerCaptor = - ArgumentCaptor.forClass(PhoneStateListener.class); - verify(mTelephonyManager).listen(phoneStateListenerCaptor.capture(), anyInt()); - mPhoneStateListener = phoneStateListenerCaptor.getValue(); } @NonNull @@ -239,6 +230,14 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public HandlerThread makeHandlerThread() { return mHandlerThread; } + + @Override + public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor( + @NonNull Context context, @NonNull Executor executor, + @NonNull NetworkStatsService service) { + + return mNetworkStatsSubscriptionsMonitor; + } }; } @@ -678,10 +677,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // TODO: support per IMSI state private void setMobileRatTypeAndWaitForIdle(int ratType) { - final ServiceState mockSs = mock(ServiceState.class); - when(mockSs.getDataNetworkType()).thenReturn(ratType); - mPhoneStateListener.onServiceStateChanged(mockSs); - + when(mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(anyString())) + .thenReturn(ratType); + mService.handleOnCollapsedRatTypeChanged(); HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT); } @@ -1118,7 +1116,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsUidDetail(buildEmptyStats()); // Register custom provider and retrieve callback. - final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider(); + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); final INetworkStatsProviderCallback cb = mService.registerNetworkStatsProvider("TEST", provider); assertNotNull(cb); @@ -1176,7 +1175,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // Register custom provider and retrieve callback. - final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider(); + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); final INetworkStatsProviderCallback cb = mService.registerNetworkStatsProvider("TEST", provider); assertNotNull(cb); diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp index 5fe94987aa65..b33995017bae 100644 --- a/tools/stats_log_api_gen/atoms_info_writer.cpp +++ b/tools/stats_log_api_gen/atoms_info_writer.cpp @@ -26,51 +26,14 @@ namespace android { namespace stats_log_api_gen { static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) { - fprintf(out, "static int UNSET_VALUE = INT_MAX;\n"); - fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n"); - - fprintf(out, "struct StateAtomFieldOptions {\n"); - fprintf(out, " std::vector<int> primaryFields;\n"); - fprintf(out, " int exclusiveField;\n"); - fprintf(out, " int defaultState = UNSET_VALUE;\n"); - fprintf(out, " int resetState = UNSET_VALUE;\n"); - fprintf(out, " bool nested;\n"); - fprintf(out, "};\n"); - fprintf(out, "\n"); - fprintf(out, "struct AtomsInfo {\n"); - fprintf(out, - " const static std::set<int> " - "kTruncatingTimestampAtomBlackList;\n"); fprintf(out, " const static std::set<int> kAtomsWithAttributionChain;\n"); - fprintf(out, - " const static std::map<int, StateAtomFieldOptions> " - "kStateAtomsFieldOptions;\n"); fprintf(out, " const static std::set<int> kWhitelistedAtoms;\n"); fprintf(out, "};\n"); fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", atoms.maxPushedAtomId); } static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) { - std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed", - "audio_state_changed", - "call_state_changed", - "phone_signal_strength_changed", - "mobile_bytes_transfer_by_fg_bg", - "mobile_bytes_transfer"}; - fprintf(out, - "const std::set<int> " - "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n"); - for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end(); - atomIt++) { - if (kTruncatingAtomNames.find((*atomIt)->name) != kTruncatingAtomNames.end()) { - const string constant = make_constant_name((*atomIt)->name); - fprintf(out, " %d, // %s\n", (*atomIt)->code, constant.c_str()); - } - } - - fprintf(out, "};\n"); - fprintf(out, "\n"); fprintf(out, "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n"); for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end(); @@ -100,49 +63,6 @@ static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) { fprintf(out, "};\n"); fprintf(out, "\n"); - fprintf(out, - "static std::map<int, StateAtomFieldOptions> " - "getStateAtomFieldOptions() {\n"); - fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n"); - fprintf(out, " StateAtomFieldOptions* opt;\n"); - for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end(); - atomIt++) { - if ((*atomIt)->primaryFields.size() == 0 && (*atomIt)->exclusiveField == 0) { - continue; - } - fprintf(out, - "\n // Adding primary and exclusive fields for atom " - "(%d)%s\n", - (*atomIt)->code, (*atomIt)->name.c_str()); - fprintf(out, " opt = &(options[%d /* %s */]);\n", (*atomIt)->code, - make_constant_name((*atomIt)->name).c_str()); - fprintf(out, " opt->primaryFields.reserve(%lu);\n", (*atomIt)->primaryFields.size()); - for (const auto& field : (*atomIt)->primaryFields) { - fprintf(out, " opt->primaryFields.push_back(%d);\n", field); - } - - fprintf(out, " opt->exclusiveField = %d;\n", (*atomIt)->exclusiveField); - if ((*atomIt)->defaultState != INT_MAX) { - fprintf(out, " opt->defaultState = %d;\n", (*atomIt)->defaultState); - } else { - fprintf(out, " opt->defaultState = UNSET_VALUE;\n"); - } - - if ((*atomIt)->triggerStateReset != INT_MAX) { - fprintf(out, " opt->resetState = %d;\n", (*atomIt)->triggerStateReset); - } else { - fprintf(out, " opt->resetState = UNSET_VALUE;\n"); - } - fprintf(out, " opt->nested = %d;\n", (*atomIt)->nested); - } - - fprintf(out, " return options;\n"); - fprintf(out, "}\n"); - - fprintf(out, - "const std::map<int, StateAtomFieldOptions> " - "AtomsInfo::kStateAtomsFieldOptions = " - "getStateAtomFieldOptions();\n"); } int write_atoms_info_header(FILE* out, const Atoms& atoms, const string& namespaceStr) { diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt index 2ecf3092035d..e55a89fddd0c 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -1,14 +1,41 @@ -# used by wifi-service -# TODO (b/153596226): Find a solution for networkstack's AIDL parcelables & interfaces. -# Parcelable class names are serialized in the wire, so renaming them -# will result in the class not being found for any parcelable received/sent from the -# wifi-service jar. +## used by service-wifi ## -# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above). -rule android.net.DhcpResultsParcelable* @0 +# Network Stack AIDL interface. +rule android.net.DhcpResultsParcelable* com.android.wifi.x.@0 +rule android.net.IIpMemoryStore* com.android.wifi.x.@0 +rule android.net.IIpMemoryStoreCallbacks* com.android.wifi.x.@0 +rule android.net.INetd* com.android.wifi.x.@0 +rule android.net.INetdUnsolicitedEventListener* com.android.wifi.x.@0 +rule android.net.INetworkStackConnector* com.android.wifi.x.@0 +rule android.net.InformationElementParcelable* com.android.wifi.x.@0 +rule android.net.InitialConfigurationParcelable* com.android.wifi.x.@0 +rule android.net.InterfaceConfigurationParcel* com.android.wifi.x.@0 +rule android.net.Layer2InformationParcelable* com.android.wifi.x.@0 +rule android.net.Layer2PacketParcelable* com.android.wifi.x.@0 +rule android.net.MarkMaskParcel* com.android.wifi.x.@0 +rule android.net.NattKeepalivePacketDataParcelable* com.android.wifi.x.@0 +rule android.net.PrivateDnsConfigParcel* com.android.wifi.x.@0 +rule android.net.ProvisioningConfigurationParcelable* com.android.wifi.x.@0 +rule android.net.ResolverParamsParcel* com.android.wifi.x.@0 +rule android.net.RouteInfoParcel* com.android.wifi.x.@0 +rule android.net.ScanResultInfoParcelable* com.android.wifi.x.@0 +rule android.net.TetherConfigParcel* com.android.wifi.x.@0 +rule android.net.TetherOffloadRuleParcel* com.android.wifi.x.@0 +rule android.net.TetherStatsParcel* com.android.wifi.x.@0 +rule android.net.UidRangeParcel* com.android.wifi.x.@0 +rule android.net.dhcp.DhcpLeaseParcelable* com.android.wifi.x.@0 +rule android.net.dhcp.DhcpServingParamsParcel* com.android.wifi.x.@0 +rule android.net.ip.IIpClient* com.android.wifi.x.@0 +rule android.net.ip.IIpClientCallbacks* com.android.wifi.x.@0 +rule android.net.ipmemorystore.Blob* com.android.wifi.x.@0 +rule android.net.ipmemorystore.IOnBlobRetrievedListener* com.android.wifi.x.@0 +rule android.net.ipmemorystore.IOnStatusListener* com.android.wifi.x.@0 +rule android.net.ipmemorystore.NetworkAttributesParcelable* com.android.wifi.x.@0 +rule android.net.ipmemorystore.SameL3NetworkResponseParcelable* com.android.wifi.x.@0 +rule android.net.ipmemorystore.StatusParcelable* com.android.wifi.x.@0 + +# Net utils (includes Network Stack helper classes). rule android.net.DhcpResults* com.android.wifi.x.@0 -# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above). -rule android.net.InterfaceConfigurationParcel* @0 rule android.net.InterfaceConfiguration* com.android.wifi.x.@0 rule android.net.IpMemoryStore* com.android.wifi.x.@0 rule android.net.NetworkMonitorManager* com.android.wifi.x.@0 @@ -19,8 +46,6 @@ rule android.net.ip.IpClientManager* com.android.wifi.x.@0 rule android.net.ip.IpClientUtil* com.android.wifi.x.@0 rule android.net.ipmemorystore.OnBlobRetrievedListener* com.android.wifi.x.@0 rule android.net.ipmemorystore.OnStatusListener* com.android.wifi.x.@0 -# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above). -rule android.net.ipmemorystore.StatusParcelable* @0 rule android.net.ipmemorystore.Status* com.android.wifi.x.@0 rule android.net.networkstack.ModuleNetworkStackClient* com.android.wifi.x.@0 rule android.net.networkstack.NetworkStackClientBase* com.android.wifi.x.@0 @@ -81,7 +106,7 @@ rule org.ksoap2.** com.android.wifi.x.@0 # Use our statically linked nanohttpd rule fi.iki.elonen.** com.android.wifi.x.@0 -# used by both framework-wifi and wifi-service +## used by both framework-wifi and service-wifi ## rule android.content.pm.BaseParceledListSlice* com.android.wifi.x.@0 rule android.content.pm.ParceledListSlice* com.android.wifi.x.@0 rule android.net.shared.Inet4AddressUtils* com.android.wifi.x.@0 |