Merge "Add missing callback to StatusBarService for inline reply"
diff --git a/Android.bp b/Android.bp
index d5e04f9..05fb3c0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -373,7 +373,6 @@
"core/java/com/android/internal/appwidget/IAppWidgetHost.aidl",
"core/java/com/android/internal/backup/IBackupTransport.aidl",
"core/java/com/android/internal/backup/IObbBackupService.aidl",
- "core/java/com/android/internal/car/ICarServiceHelper.aidl",
"core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl",
"core/java/com/android/internal/net/INetworkWatchlistManager.aidl",
"core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl",
@@ -579,7 +578,6 @@
"wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl",
"wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl",
"wifi/java/android/net/wifi/IWifiScanner.aidl",
- "wifi/java/android/net/wifi/IRttManager.aidl",
"packages/services/PacProcessor/com/android/net/IProxyService.aidl",
"packages/services/Proxy/com/android/net/IProxyCallback.aidl",
"packages/services/Proxy/com/android/net/IProxyPortListener.aidl",
@@ -672,8 +670,9 @@
"android.hardware.tv.input-V1.0-java-constants",
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
- "android.hardware.vibrator-V1.0-java-constants",
- "android.hardware.vibrator-V1.1-java-constants",
+ "android.hardware.vibrator-V1.0-java",
+ "android.hardware.vibrator-V1.1-java",
+ "android.hardware.vibrator-V1.2-java",
"android.hardware.wifi-V1.0-java-constants",
"android.hardware.radio-V1.0-java",
"android.hardware.usb.gadget-V1.0-java",
diff --git a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
new file mode 100644
index 0000000..f8fd51d
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// Due to b/71353150, you might get "java.lang.AssertionError: Binder ProxyMap has too many
+// entries", but it's flaky. Adding "Runtime.getRuntime().gc()" between each iteration solves
+// the problem, but it doesn't seem like it's currently needed.
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PendingIntentPerfTest {
+
+ private Context mContext;
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private Intent mIntent;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mIntent = StubActivity.createLaunchIntent(mContext);
+ }
+
+ /**
+ * Benchmark time to create a PendingIntent.
+ */
+ @Test
+ public void create() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ state.resumeTiming();
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+ 0);
+
+ state.pauseTiming();
+ pendingIntent.cancel();
+ state.resumeTiming();
+ }
+ }
+
+ /**
+ * Benchmark time to create a PendingIntent with FLAG_CANCEL_CURRENT, already having an active
+ * PendingIntent.
+ */
+ @Test
+ public void createWithCancelFlag() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0,
+ mIntent, 0);
+ state.resumeTiming();
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ state.pauseTiming();
+ pendingIntent.cancel();
+ state.resumeTiming();
+ }
+ }
+
+ /**
+ * Benchmark time to create a PendingIntent with FLAG_UPDATE_CURRENT, already having an active
+ * PendingIntent.
+ */
+ @Test
+ public void createWithUpdateFlag() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0,
+ mIntent, 0);
+ state.resumeTiming();
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ state.pauseTiming();
+ previousPendingIntent.cancel();
+ pendingIntent.cancel();
+ state.resumeTiming();
+ }
+ }
+
+ /**
+ * Benchmark time to cancel a PendingIntent.
+ */
+ @Test
+ public void cancel() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+ mIntent, 0);
+ state.resumeTiming();
+
+ pendingIntent.cancel();
+ }
+ }
+}
+
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java
new file mode 100644
index 0000000..60c6d89
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.text;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutMultithreadPerfTest {
+ private static final int WORD_LENGTH = 9; // Random word has 9 characters.
+ private static final int WORDS_IN_LINE = 8; // Roughly, 8 words in a line.
+ private static final boolean NO_STYLE_TEXT = false;
+
+ private static TextPaint PAINT = new TextPaint();
+ private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();
+
+ public StaticLayoutMultithreadPerfTest() {}
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private CountDownLatch mStartLatch;
+ private AtomicBoolean mThreadState; // True for running, False for stopped.
+
+ private static final long TIMEOUT_MS = 5000;
+
+ private Thread[] startBackgroundThread(int numOfThreads) {
+ mStartLatch = new CountDownLatch(numOfThreads);
+ mThreadState = new AtomicBoolean(true);
+
+ Thread[] threads = new Thread[numOfThreads];
+ for (int i = 0; i < numOfThreads; ++i) {
+ final int seed = i + 1;
+ threads[i] = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ final TextPerfUtils util = new TextPerfUtils();
+ util.resetRandom(seed);
+
+ mStartLatch.countDown();
+ while (mThreadState.get()) {
+ final CharSequence text = util.nextRandomParagraph(
+ WORD_LENGTH, NO_STYLE_TEXT);
+ StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
+ .build();
+ }
+ }
+ });
+ }
+
+ for (int i = 0; i < numOfThreads; ++i) {
+ threads[i].start();
+ }
+
+ try {
+ mStartLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return threads;
+ }
+
+ private void finishThreads(Thread[] threads) {
+ mThreadState.set(false);
+ for (Thread thread : threads) {
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ mStartLatch = null;
+ mThreadState = null;
+ }
+
+ private void runRandomTest(int numOfTotalThreads) {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final TextPerfUtils util = new TextPerfUtils();
+ Thread[] threads = startBackgroundThread(numOfTotalThreads - 1);
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final CharSequence text = util.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+ state.resumeTiming();
+
+ StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .build();
+ }
+ finishThreads(threads);
+ }
+
+ @Test
+ public void testCreate_RandomText_Thread_1() {
+ runRandomTest(1);
+ }
+
+ @Test
+ public void testCreate_RandomText_Thread_2() {
+ runRandomTest(2);
+ }
+
+ @Test
+ public void testCreate_RandomText_Thread_4() {
+ runRandomTest(4);
+ }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
index 6012f4b1..8f03f7e 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
@@ -17,6 +17,14 @@
package android.perftests.utils;
import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
public class StubActivity extends Activity {
-}
\ No newline at end of file
+ public static Intent createLaunchIntent(Context context) {
+ final Intent intent = new Intent();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(context, StubActivity.class);
+ return intent;
+ }
+}
diff --git a/api/current.txt b/api/current.txt
index 1ffba87..3944b45 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -73,6 +73,7 @@
field public static final java.lang.String DUMP = "android.permission.DUMP";
field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
+ field public static final java.lang.String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -6493,7 +6494,6 @@
method public boolean isNetworkLoggingEnabled(android.content.ComponentName);
method public boolean isOverrideApnEnabled(android.content.ComponentName);
method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public boolean isPrintingEnabled();
method public boolean isProfileOwnerApp(java.lang.String);
method public boolean isProvisioningAllowed(java.lang.String);
method public boolean isResetPasswordTokenActive(android.content.ComponentName);
@@ -6566,7 +6566,6 @@
method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
- method public void setPrintingEnabled(android.content.ComponentName, boolean);
method public void setProfileEnabled(android.content.ComponentName);
method public void setProfileName(android.content.ComponentName, java.lang.String);
method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
@@ -6741,6 +6740,7 @@
field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
field public static final int TAG_CERT_AUTHORITY_INSTALLED = 210029; // 0x3346d
field public static final int TAG_CERT_AUTHORITY_REMOVED = 210030; // 0x3346e
+ field public static final int TAG_CRYPTO_SELF_TEST_COMPLETED = 210031; // 0x3346f
field public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = 210021; // 0x33465
field public static final int TAG_KEYGUARD_DISMISSED = 210006; // 0x33456
field public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = 210007; // 0x33457
@@ -14605,7 +14605,7 @@
method public final android.graphics.Rect copyBounds();
method public static android.graphics.drawable.Drawable createFromPath(java.lang.String);
method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String);
- method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options);
+ method public static deprecated android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options);
method public static android.graphics.drawable.Drawable createFromStream(java.io.InputStream, java.lang.String);
method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -16296,7 +16296,7 @@
public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult {
method public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults();
- method public <T> T getPhysicalCameraKey(android.hardware.camera2.CaptureResult.Key<T>, java.lang.String);
+ method public java.util.Map<java.lang.String, android.hardware.camera2.CaptureResult> getPhysicalCameraResults();
}
}
@@ -33140,6 +33140,7 @@
field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset";
field public static final java.lang.String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
+ field public static final java.lang.String DISALLOW_PRINTING = "no_printing";
field public static final java.lang.String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user";
field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
@@ -40505,6 +40506,7 @@
public final class Call {
method public void answer(int);
method public void conference(android.telecom.Call);
+ method public void deflect(android.net.Uri);
method public void disconnect();
method public java.util.List<java.lang.String> getCannedTextResponses();
method public java.util.List<android.telecom.Call> getChildren();
@@ -40615,6 +40617,7 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
+ field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
@@ -40762,6 +40765,7 @@
method public void onAnswer();
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onCallEvent(java.lang.String, android.os.Bundle);
+ method public void onDeflect(android.net.Uri);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
method public void onHandoverComplete();
@@ -40830,6 +40834,7 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
+ field public static final int CAPABILITY_SUPPORT_DEFLECT = 33554432; // 0x2000000
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
@@ -48147,8 +48152,6 @@
method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect);
method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect);
- method protected void onDebugDraw(android.graphics.Canvas);
- method protected void onDebugDrawMargins(android.graphics.Canvas, android.graphics.Paint);
method public boolean onInterceptHoverEvent(android.view.MotionEvent);
method public boolean onInterceptTouchEvent(android.view.MotionEvent);
method protected abstract void onLayout(boolean, int, int, int, int);
@@ -48222,7 +48225,6 @@
ctor public ViewGroup.LayoutParams(android.content.Context, android.util.AttributeSet);
ctor public ViewGroup.LayoutParams(int, int);
ctor public ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams);
- method public void onDebugDraw(android.view.View, android.graphics.Canvas, android.graphics.Paint);
method public void resolveLayoutDirection(int);
method protected void setBaseAttributes(android.content.res.TypedArray, int, int);
field public static final deprecated int FILL_PARENT = -1; // 0xffffffff
diff --git a/api/system-current.txt b/api/system-current.txt
index 70148d9..3dbb333 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -30,8 +30,8 @@
field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE";
field public static final java.lang.String BIND_TELEPHONY_DATA_SERVICE = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
- field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
field public static final java.lang.String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
+ field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
@@ -362,10 +362,12 @@
public final class StatsManager {
method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+ method public boolean addConfiguration(long, byte[]);
method public byte[] getData(long);
method public byte[] getMetadata();
method public boolean removeConfiguration(long);
method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
+ method public boolean setDataFetchOperation(long, android.app.PendingIntent);
field public static final java.lang.String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
field public static final java.lang.String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
@@ -687,6 +689,12 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
}
+ public static final class UsageEvents.Event {
+ method public int getStandbyBucket();
+ field public static final int NOTIFICATION_SEEN = 10; // 0xa
+ field public static final int STANDBY_BUCKET_CHANGED = 11; // 0xb
+ }
+
public final class UsageStatsManager {
method public int getAppStandbyBucket(java.lang.String);
method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
@@ -791,7 +799,7 @@
field public static final java.lang.String STATS_MANAGER = "stats";
field public static final java.lang.String SYSTEM_UPDATE_SERVICE = "system_update";
field public static final java.lang.String VR_SERVICE = "vrmanager";
- field public static final java.lang.String WIFI_RTT_SERVICE = "rttmanager";
+ field public static final deprecated java.lang.String WIFI_RTT_SERVICE = "rttmanager";
field public static final java.lang.String WIFI_SCANNING_SERVICE = "wifiscanner";
}
@@ -821,6 +829,7 @@
field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
field public static final java.lang.String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final java.lang.String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
@@ -2516,6 +2525,7 @@
method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
method public deprecated int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
method public int requestAudioFocus(android.media.AudioFocusRequest, android.media.audiopolicy.AudioPolicy);
+ method public void setFocusRequestResult(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy);
method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
@@ -3090,7 +3100,7 @@
package android.net.wifi {
- public class RttManager {
+ public deprecated class RttManager {
method public void disableResponder(android.net.wifi.RttManager.ResponderCallback);
method public void enableResponder(android.net.wifi.RttManager.ResponderCallback);
method public deprecated android.net.wifi.RttManager.Capabilities getCapabilities();
@@ -3166,22 +3176,22 @@
field public int supportedType;
}
- public static class RttManager.ParcelableRttParams implements android.os.Parcelable {
+ public static deprecated class RttManager.ParcelableRttParams implements android.os.Parcelable {
field public android.net.wifi.RttManager.RttParams[] mParams;
}
- public static class RttManager.ParcelableRttResults implements android.os.Parcelable {
+ public static deprecated class RttManager.ParcelableRttResults implements android.os.Parcelable {
ctor public RttManager.ParcelableRttResults(android.net.wifi.RttManager.RttResult[]);
field public android.net.wifi.RttManager.RttResult[] mResults;
}
- public static abstract class RttManager.ResponderCallback {
+ public static abstract deprecated class RttManager.ResponderCallback {
ctor public RttManager.ResponderCallback();
method public abstract void onResponderEnableFailure(int);
method public abstract void onResponderEnabled(android.net.wifi.RttManager.ResponderConfig);
}
- public static class RttManager.ResponderConfig implements android.os.Parcelable {
+ public static deprecated class RttManager.ResponderConfig implements android.os.Parcelable {
ctor public RttManager.ResponderConfig();
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -3194,7 +3204,7 @@
field public int preamble;
}
- public static class RttManager.RttCapabilities implements android.os.Parcelable {
+ public static deprecated class RttManager.RttCapabilities implements android.os.Parcelable {
ctor public RttManager.RttCapabilities();
field public int bwSupported;
field public boolean lciSupported;
@@ -3209,13 +3219,13 @@
field public boolean twoSided11McRttSupported;
}
- public static abstract interface RttManager.RttListener {
+ public static abstract deprecated interface RttManager.RttListener {
method public abstract void onAborted();
method public abstract void onFailure(int, java.lang.String);
method public abstract void onSuccess(android.net.wifi.RttManager.RttResult[]);
}
- public static class RttManager.RttParams {
+ public static deprecated class RttManager.RttParams {
ctor public RttManager.RttParams();
field public boolean LCIRequest;
field public boolean LCRRequest;
@@ -3239,7 +3249,7 @@
field public boolean secure;
}
- public static class RttManager.RttResult {
+ public static deprecated class RttManager.RttResult {
ctor public RttManager.RttResult();
field public android.net.wifi.RttManager.WifiInformationElement LCI;
field public android.net.wifi.RttManager.WifiInformationElement LCR;
@@ -3276,7 +3286,7 @@
field public deprecated int tx_rate;
}
- public static class RttManager.WifiInformationElement {
+ public static deprecated class RttManager.WifiInformationElement {
ctor public RttManager.WifiInformationElement();
field public byte[] data;
field public byte id;
@@ -5034,22 +5044,19 @@
}
public class UiccSlotInfo implements android.os.Parcelable {
- ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int);
+ ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int, int);
method public int describeContents();
method public java.lang.String getCardId();
method public int getCardStateInfo();
method public boolean getIsActive();
method public boolean getIsEuicc();
+ method public int getLogicalSlotIdx();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1
field public static final int CARD_STATE_INFO_ERROR = 3; // 0x3
field public static final int CARD_STATE_INFO_PRESENT = 2; // 0x2
field public static final int CARD_STATE_INFO_RESTRICTED = 4; // 0x4
field public static final android.os.Parcelable.Creator<android.telephony.UiccSlotInfo> CREATOR;
- field public final java.lang.String cardId;
- field public final int cardStateInfo;
- field public final boolean isActive;
- field public final boolean isEuicc;
}
public abstract class VisualVoicemailService extends android.app.Service {
@@ -5710,6 +5717,7 @@
ctor public ImsCallSessionImplBase();
method public void accept(int, android.telephony.ims.ImsStreamMediaProfile);
method public void close();
+ method public void deflect(java.lang.String);
method public void extendToConference(java.lang.String[]);
method public java.lang.String getCallId();
method public android.telephony.ims.ImsCallProfile getCallProfile();
diff --git a/api/test-current.txt b/api/test-current.txt
index ac4ce3b..b02da04 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -211,6 +211,7 @@
method public abstract boolean isPermissionReviewModeEnabled();
field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final java.lang.String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
+ field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
}
public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -1037,6 +1038,7 @@
public class AccessibilityNodeInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
+ method public void writeToParcelNoRecycle(android.os.Parcel, int);
}
public final class AccessibilityWindowInfo implements android.os.Parcelable {
@@ -1077,6 +1079,12 @@
field public static final int MODE_SPINNER = 1; // 0x1
}
+ public final class Magnifier {
+ method public android.graphics.Bitmap getContent();
+ method public static android.graphics.PointF getMagnifierDefaultSize();
+ method public android.graphics.Rect getWindowPositionOnScreen();
+ }
+
public class NumberPicker extends android.widget.LinearLayout {
method public java.lang.CharSequence getDisplayedValueForCurrentSelection();
}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 1d5ab59..654036e 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -46,6 +46,7 @@
checkIncidentPermissions(const IncidentReportArgs& args)
{
uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ pid_t callingPid = IPCThreadState::self()->getCallingPid();
if (callingUid == AID_ROOT || callingUid == AID_SHELL) {
// root doesn't have permission.DUMP if don't do this!
return Status::ok();
@@ -54,13 +55,13 @@
// checking calling permission.
if (!checkCallingPermission(DUMP_PERMISSION)) {
ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP",
- IPCThreadState::self()->getCallingPid(), callingUid);
+ callingPid, callingUid);
return Status::fromExceptionCode(Status::EX_SECURITY,
"Calling process does not have permission: android.permission.DUMP");
}
if (!checkCallingPermission(USAGE_STATS_PERMISSION)) {
ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS",
- IPCThreadState::self()->getCallingPid(), callingUid);
+ callingPid, callingUid);
return Status::fromExceptionCode(Status::EX_SECURITY,
"Calling process does not have permission: android.permission.USAGE_STATS");
}
@@ -68,13 +69,17 @@
// checking calling request uid permission.
switch (args.dest()) {
case DEST_LOCAL:
- if (callingUid != AID_SHELL || callingUid != AID_ROOT) {
+ if (callingUid != AID_SHELL && callingUid != AID_ROOT) {
+ ALOGW("Calling pid %d and uid %d does not have permission to get local data.",
+ callingPid, callingUid);
return Status::fromExceptionCode(Status::EX_SECURITY,
"Calling process does not have permission to get local data.");
}
case DEST_EXPLICIT:
- if (callingUid != AID_SHELL || callingUid != AID_ROOT ||
- callingUid != AID_STATSD || callingUid != AID_SYSTEM) {
+ if (callingUid != AID_SHELL && callingUid != AID_ROOT &&
+ callingUid != AID_STATSD && callingUid != AID_SYSTEM) {
+ ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
+ callingPid, callingUid);
return Status::fromExceptionCode(Status::EX_SECURITY,
"Calling process does not have permission to get explicit data.");
}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index b0019ac..90158a0 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -44,6 +44,7 @@
src/external/KernelUidCpuActiveTimeReader.cpp \
src/external/KernelUidCpuClusterTimeReader.cpp \
src/external/StatsPullerManagerImpl.cpp \
+ src/external/puller_util.cpp \
src/logd/LogEvent.cpp \
src/logd/LogListener.cpp \
src/logd/LogReader.cpp \
@@ -65,6 +66,7 @@
src/storage/StorageManager.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
+ src/subscriber/IncidentdReporter.cpp \
src/subscriber/SubscriberReporter.cpp \
src/HashableDimensionKey.cpp \
src/guardrail/MemoryLeakTrackUtil.cpp \
@@ -174,6 +176,7 @@
tests/AnomalyMonitor_test.cpp \
tests/anomaly/AnomalyTracker_test.cpp \
tests/ConfigManager_test.cpp \
+ tests/external/puller_util_test.cpp \
tests/indexed_priority_queue_test.cpp \
tests/LogEntryMatcher_test.cpp \
tests/LogReader_test.cpp \
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index f0eaeff..8483b02 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -181,7 +181,9 @@
return toString().compare(that.toString()) < 0;
};
-
+bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) {
+ return EqualsTo(s1, s2);
+}
} // namespace statsd
} // namespace os
} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index a4066aa..7662c40 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "statslog.h"
@@ -32,6 +32,7 @@
#include <log/log_event_list.h>
#include <utils/Errors.h>
+#include <utils/SystemClock.h>
using namespace android;
using android::base::StringPrintf;
@@ -60,6 +61,8 @@
// for ConfigMetricsReport
const int FIELD_ID_METRICS = 1;
const int FIELD_ID_UID_MAP = 2;
+const int FIELD_ID_LAST_REPORT_NANOS = 3;
+const int FIELD_ID_CURRENT_REPORT_NANOS = 4;
#define STATS_DATA_DIR "/data/misc/stats-data"
@@ -146,6 +149,12 @@
return;
}
+ long curTime = time(nullptr);
+ if (curTime - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) {
+ mStatsPullerManager.ClearPullerCacheIfNecessary(curTime);
+ mLastPullerCacheClearTimeSec = curTime;
+ }
+
if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) {
// Map the isolated uid to host uid if necessary.
mapIsolatedUidToHostUidIfNecessaryLocked(event);
@@ -160,7 +169,7 @@
void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
- ALOGD("Updated configuration for key %s", key.ToString().c_str());
+ VLOG("Updated configuration for key %s", key.ToString().c_str());
sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
@@ -258,6 +267,12 @@
uidMap.SerializeToArray(&uidMapBuffer[0], uidMapSize);
proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP, uidMapBuffer, uidMapSize);
+ // Fill in the timestamps.
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_NANOS,
+ (long long)it->second->getLastReportTimeNs());
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_NANOS,
+ (long long)::android::elapsedRealtimeNano());
+
// End of ConfigMetricsReport (reports).
proto.end(reportsToken);
@@ -290,6 +305,10 @@
StatsdStats::getInstance().noteConfigRemoved(key);
mLastBroadcastTimes.erase(key);
+
+ if (mMetricsManagers.empty()) {
+ mStatsPullerManager.ForceClearPullerCache();
+ }
}
void StatsLogProcessor::flushIfNecessaryLocked(
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 7642aafa..8bbcd75 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -21,6 +21,7 @@
#include "logd/LogReader.h"
#include "metrics/MetricsManager.h"
#include "packages/UidMap.h"
+#include "external/StatsPullerManager.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -75,6 +76,8 @@
sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid.
+ StatsPullerManager mStatsPullerManager;
+
sp<AnomalyMonitor> mAnomalyMonitor;
void onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData);
@@ -96,11 +99,14 @@
const long mTimeBaseSec;
+ long mLastPullerCacheClearTimeSec = 0;
+
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
- FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration);
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 32da94f..c24efec 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "StatsService.h"
@@ -77,18 +77,18 @@
: mAnomalyMonitor(new AnomalyMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS))
{
mUidMap = new UidMap();
+ StatsPuller::SetUidMap(mUidMap);
mConfigManager = new ConfigManager();
mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, time(nullptr), [this](const ConfigKey& key) {
sp<IStatsCompanionService> sc = getStatsCompanionService();
auto receiver = mConfigManager->GetConfigReceiver(key);
if (sc == nullptr) {
VLOG("Could not find StatsCompanionService");
- } else if (receiver.first.size() == 0) {
+ } else if (receiver == nullptr) {
VLOG("Statscompanion could not find a broadcast receiver for %s",
key.ToString().c_str());
} else {
- sc->sendBroadcast(String16(receiver.first.c_str()),
- String16(receiver.second.c_str()));
+ sc->sendDataBroadcast(receiver);
}
});
@@ -240,6 +240,10 @@
if (!args[0].compare(String8("log-app-hook"))) {
return cmd_log_app_hook(out, args);
}
+
+ if (!args[0].compare(String8("clear-puller-cache"))) {
+ return cmd_clear_puller_cache(out);
+ }
}
print_cmd_help(out);
@@ -320,6 +324,10 @@
fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats print-stats\n");
fprintf(out, " Prints some basic stats.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmd stats clear-puller-cache\n");
+ fprintf(out, " Clear cached puller data.\n");
}
status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) {
@@ -358,12 +366,14 @@
}
auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, StrToInt64(name)));
sp<IStatsCompanionService> sc = getStatsCompanionService();
- if (sc != nullptr) {
- sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str()));
+ if (sc == nullptr) {
+ VLOG("Could not access statsCompanion");
+ } else if (receiver == nullptr) {
+ VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str())
+ } else {
+ sc->sendDataBroadcast(receiver);
VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
args[2].c_str());
- } else {
- VLOG("Could not access statsCompanion");
}
return NO_ERROR;
@@ -602,9 +612,15 @@
}
status_t StatsService::cmd_clear_puller_cache(FILE* out) {
- mStatsPullerManager.ClearPullerCache();
- fprintf(out, "Puller cached data removed!\n");
- return NO_ERROR;
+ IPCThreadState* ipc = IPCThreadState::self();
+ VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ int cleared = mStatsPullerManager.ForceClearPullerCache();
+ fprintf(out, "Puller removed %d cached data!\n", cleared);
+ return NO_ERROR;
+ } else {
+ return PERMISSION_DENIED;
+ }
}
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
@@ -785,7 +801,6 @@
Status StatsService::addConfiguration(int64_t key,
const vector <uint8_t>& config,
- const String16& package, const String16& cls,
bool* success) {
IPCThreadState* ipc = IPCThreadState::self();
if (checkCallingPermission(String16(kPermissionDump))) {
@@ -796,8 +811,33 @@
return Status::ok();
}
mConfigManager->UpdateConfig(configKey, cfg);
- mConfigManager->SetConfigReceiver(configKey, string(String8(package).string()),
- string(String8(cls).string()));
+ *success = true;
+ return Status::ok();
+ } else {
+ *success = false;
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
+Status StatsService::removeDataFetchOperation(int64_t key, bool* success) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->RemoveConfigReceiver(configKey);
+ *success = true;
+ return Status::ok();
+ } else {
+ *success = false;
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
+Status StatsService::setDataFetchOperation(int64_t key, const sp<android::IBinder>& intentSender,
+ bool* success) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->SetConfigReceiver(configKey, intentSender);
*success = true;
return Status::ok();
} else {
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 109752b..3dc19fe 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -90,9 +90,19 @@
* Binder call to let clients send a configuration and indicate they're interested when they
* should requestData for this configuration.
*/
- virtual Status addConfiguration(int64_t key, const vector <uint8_t>& config,
- const String16& package, const String16& cls, bool* success)
- override;
+ virtual Status addConfiguration(int64_t key, const vector<uint8_t>& config,
+ bool* success) override;
+
+ /**
+ * Binder call to let clients register the data fetch operation for a configuration.
+ */
+ virtual Status setDataFetchOperation(int64_t key, const sp<android::IBinder>& intentSender,
+ bool* success) override;
+
+ /**
+ * Binder call to remove the data fetch operation for the specified config key.
+ */
+ virtual Status removeDataFetchOperation(int64_t key, bool* success) override;
/**
* Binder call to allow clients to remove the specified configuration.
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
index 4912648..72d29d0 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true
+#define DEBUG false
#include "Log.h"
#include "anomaly/AnomalyMonitor.h"
@@ -36,10 +36,10 @@
sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
mStatsCompanionService = statsCompanionService;
if (statsCompanionService == nullptr) {
- if (DEBUG) ALOGD("Erasing link to statsCompanionService");
+ VLOG("Erasing link to statsCompanionService");
return;
}
- if (DEBUG) ALOGD("Creating link to statsCompanionService");
+ VLOG("Creating link to statsCompanionService");
const sp<const AnomalyAlarm> top = mPq.top();
if (top != nullptr) {
updateRegisteredAlarmTime_l(top->timestampSec);
@@ -58,7 +58,7 @@
return;
}
// TODO: Ensure that refractory period is respected.
- if (DEBUG) ALOGD("Adding alarm with time %u", alarm->timestampSec);
+ VLOG("Adding alarm with time %u", alarm->timestampSec);
mPq.push(alarm);
if (mRegisteredAlarmTimeSec < 1 ||
alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) {
@@ -72,16 +72,16 @@
ALOGW("Asked to remove a null alarm.");
return;
}
- if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec);
+ VLOG("Removing alarm with time %u", alarm->timestampSec);
bool wasPresent = mPq.remove(alarm);
if (!wasPresent) return;
if (mPq.empty()) {
- if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
+ VLOG("Queue is empty. Cancel any alarm.");
cancelRegisteredAlarmTime_l();
return;
}
uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec;
- if (DEBUG) ALOGD("Soonest alarm is %u", soonestAlarmTimeSec);
+ VLOG("Soonest alarm is %u", soonestAlarmTimeSec);
if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) {
updateRegisteredAlarmTime_l(soonestAlarmTimeSec);
}
@@ -91,7 +91,7 @@
// updates to the registered alarm.
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::popSoonerThan(
uint32_t timestampSec) {
- if (DEBUG) ALOGD("Removing alarms with time <= %u", timestampSec);
+ VLOG("Removing alarms with time <= %u", timestampSec);
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> oldAlarms;
std::lock_guard<std::mutex> lock(mLock);
@@ -103,7 +103,7 @@
// Always update registered alarm time (if anything has changed).
if (!oldAlarms.empty()) {
if (mPq.empty()) {
- if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
+ VLOG("Queue is empty. Cancel any alarm.");
cancelRegisteredAlarmTime_l();
} else {
// Always update the registered alarm in this case (unlike remove()).
@@ -114,7 +114,7 @@
}
void AnomalyMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
- if (DEBUG) ALOGD("Updating reg alarm time to %u", timestampSec);
+ VLOG("Updating reg alarm time to %u", timestampSec);
mRegisteredAlarmTimeSec = timestampSec;
if (mStatsCompanionService != nullptr) {
mStatsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec));
@@ -123,7 +123,7 @@
}
void AnomalyMonitor::cancelRegisteredAlarmTime_l() {
- if (DEBUG) ALOGD("Cancelling reg alarm.");
+ VLOG("Cancelling reg alarm.");
mRegisteredAlarmTimeSec = 0;
if (mStatsCompanionService != nullptr) {
mStatsCompanionService->cancelAnomalyAlarm();
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index c84a5b4..63f6e2a 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -14,18 +14,16 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "AnomalyTracker.h"
#include "external/Perfetto.h"
-#include "guardrail/StatsdStats.h"
#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+#include "guardrail/StatsdStats.h"
+#include "subscriber/IncidentdReporter.h"
#include "subscriber/SubscriberReporter.h"
-#include <android/os/IIncidentManager.h>
-#include <android/os/IncidentReportArgs.h>
-#include <binder/IServiceManager.h>
#include <statslog.h>
#include <time.h>
@@ -35,20 +33,17 @@
// TODO: Get rid of bucketNumbers, and return to the original circular array method.
AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
- : mAlert(alert),
- mConfigKey(configKey),
- mNumOfPastBuckets(mAlert.num_buckets() - 1) {
+ : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
VLOG("AnomalyTracker() called");
if (mAlert.num_buckets() <= 0) {
- ALOGE("Cannot create AnomalyTracker with %lld buckets",
- (long long)mAlert.num_buckets());
+ ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
return;
}
if (!mAlert.has_trigger_if_sum_gt()) {
ALOGE("Cannot create AnomalyTracker without threshold");
return;
}
- resetStorage(); // initialization
+ resetStorage(); // initialization
}
AnomalyTracker::~AnomalyTracker() {
@@ -171,8 +166,8 @@
// TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
addPastBucket(key, 0, currentBucketNum - 1);
}
- return mAlert.has_trigger_if_sum_gt()
- && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
+ return mAlert.has_trigger_if_sum_gt() &&
+ getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
}
void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) {
@@ -188,7 +183,7 @@
if (!mSubscriptions.empty()) {
if (mAlert.has_id()) {
- ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
+ ALOGI("An anomaly (%llu) has occurred! Informing subscribers.", mAlert.id());
informSubscribers(key);
} else {
ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
@@ -233,44 +228,26 @@
return;
}
- std::set<int> incidentdSections;
-
for (const Subscription& subscription : mSubscriptions) {
switch (subscription.subscriber_information_case()) {
case Subscription::SubscriberInformationCase::kIncidentdDetails:
- for (int i = 0; i < subscription.incidentd_details().section_size(); i++) {
- incidentdSections.insert(subscription.incidentd_details().section(i));
+ if (!GenerateIncidentReport(subscription.incidentd_details(), mAlert, mConfigKey)) {
+ ALOGW("Failed to generate incident report.");
}
break;
case Subscription::SubscriberInformationCase::kPerfettoDetails:
- CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
+ if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) {
+ ALOGW("Failed to generate prefetto traces.");
+ }
break;
case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
- SubscriberReporter::getInstance()
- .alertBroadcastSubscriber(mConfigKey, subscription, key);
+ SubscriberReporter::getInstance().alertBroadcastSubscriber(mConfigKey, subscription,
+ key);
break;
default:
break;
}
}
- if (!incidentdSections.empty()) {
- sp<IIncidentManager> service = interface_cast<IIncidentManager>(
- defaultServiceManager()->getService(android::String16("incident")));
- if (service != NULL) {
- IncidentReportArgs incidentReport;
- for (const auto section : incidentdSections) {
- incidentReport.addSection(section);
- }
- android::os::IncidentHeaderProto header;
- header.set_alert_id(mAlert.id());
- header.mutable_config_key()->set_uid(mConfigKey.GetUid());
- header.mutable_config_key()->set_id(mConfigKey.GetId());
- incidentReport.addHeader(header);
- service->reportIncident(incidentReport);
- } else {
- ALOGW("Couldn't get the incident service.");
- }
- }
}
} // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index f01a97f..3be959d 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -16,22 +16,24 @@
#pragma once
+#include <memory> // unique_ptr
+
+#include <stdlib.h>
+
#include <gtest/gtest_prod.h>
+#include <utils/RefBase.h>
+
#include "AnomalyMonitor.h"
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
#include "stats_util.h" // HashableDimensionKey and DimToValMap
-#include <memory> // unique_ptr
-#include <stdlib.h>
-#include <utils/RefBase.h>
-
namespace android {
namespace os {
namespace statsd {
-using std::unordered_map;
using std::shared_ptr;
+using std::unordered_map;
// Does NOT allow negative values.
class AnomalyTracker : public virtual RefBase {
@@ -60,12 +62,11 @@
// Detects the alert and informs the incidentd when applicable.
void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
- const MetricDimensionKey& key,
- const int64_t& currentBucketValue);
+ const MetricDimensionKey& key, const int64_t& currentBucketValue);
// Init the AnomalyMonitor which is shared across anomaly trackers.
virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
- return; // Base AnomalyTracker class has no need for the AnomalyMonitor.
+ return; // Base AnomalyTracker class has no need for the AnomalyMonitor.
}
// Helper function to return the sum value of past buckets at given dimension.
@@ -92,9 +93,10 @@
// Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
// and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
- virtual void informAlarmsFired(const uint64_t& timestampNs,
+ virtual void informAlarmsFired(
+ const uint64_t& timestampNs,
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
- return; // The base AnomalyTracker class doesn't have alarms.
+ return; // The base AnomalyTracker class doesn't have alarms.
}
protected:
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
index bbee9fa..fa0bc0c 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "DurationAnomalyTracker.h"
@@ -52,8 +52,7 @@
}
void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
- const uint64_t& timestampNs) {
-
+ const uint64_t& timestampNs) {
uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
@@ -86,15 +85,15 @@
}
}
-void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
+void DurationAnomalyTracker::informAlarmsFired(
+ const uint64_t& timestampNs,
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
-
if (firedAlarms.empty() || mAlarms.empty()) return;
// Find the intersection of firedAlarms and mAlarms.
// The for loop is inefficient, since it loops over all keys, but that's okay since it is very
// seldomly called. The alternative would be having AnomalyAlarms store information about the
- // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is
- // rarely ever called.
+ // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that
+ // is rarely ever called.
unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
for (const auto& kv : mAlarms) {
if (firedAlarms.count(kv.second) > 0) {
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 052fdf57..ba687da 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -53,7 +53,8 @@
// and removes it from firedAlarms.
// Note that this will generally be called from a different thread from the other functions;
// the caller is responsible for thread safety.
- void informAlarmsFired(const uint64_t& timestampNs,
+ void informAlarmsFired(
+ const uint64_t& timestampNs,
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
protected:
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index ed570e7..4c6a36b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -24,6 +24,7 @@
import "frameworks/base/core/proto/android/app/enums.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/telecomm/enums.proto";
import "frameworks/base/core/proto/android/telephony/enums.proto";
import "frameworks/base/core/proto/android/view/enums.proto";
@@ -97,6 +98,8 @@
BootSequenceReported boot_sequence_reported = 57;
DaveyOccurred davey_occurred = 58;
OverlayStateChanged overlay_state_changed = 59;
+ ForegroundServiceStateChanged foreground_service_state_changed = 60;
+ CallStateChanged call_state_changed = 61;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -724,6 +727,33 @@
optional int64 time_since_last_boot = 6;
}
+
+/**
+ * Logs call state and disconnect cause (if applicable).
+ *
+ * Logged from:
+ * packages/services/Telecomm/src/com/android/server/telecom/Call.java
+ */
+message CallStateChanged {
+ // The state of the call. Eg. DIALING, ACTIVE, ON_HOLD, DISCONNECTED.
+ // From frameworks/base/core/proto/android/telecomm/enums.proto.
+ optional android.telecom.CallStateEnum call_state = 1;
+
+ // The reason the call disconnected. Eg. ERROR, MISSED, REJECTED, BUSY.
+ // This value is only applicable when the call_state is DISCONNECTED, and
+ // should always be UNKNOWN if the call_state is not DISCONNECTED.
+ // From frameworks/base/core/proto/android/telecomm/enums.proto.
+ optional android.telecom.DisconnectCauseEnum disconnect_cause = 2;
+
+ // True if the call is self-managed, which are apps that use the
+ // telecom infrastructure to make their own calls.
+ optional bool self_managed = 3;
+
+ // True if call is external. External calls are calls on connected Wear
+ // devices but show up in Telecom so the user can pull them onto the device.
+ optional bool external_call = 4;
+}
+
/**
* Logs the duration of a davey (jank of >=700ms) when it occurs
*
@@ -998,6 +1028,157 @@
optional State state = 4;
}
+/*
+ * Logs foreground service starts and stops.
+ * Note that this is not when a service starts or stops, but when it is
+ * considered foreground.
+ * Logged from
+ * //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ForegroundServiceStateChanged {
+ optional int32 uid = 1;
+ // package_name + "/" + class_name
+ optional string short_name = 2;
+
+ enum State {
+ ENTER = 1;
+ EXIT = 2;
+ }
+ optional State state = 3;
+}
+
+/**
+ * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky
+ * behavior in its own uid. However, the metrics of these isolated uid's almost always should be
+ * attributed back to the parent (host) uid. One example is Chrome.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message IsolatedUidChanged {
+ // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid.
+ optional int32 parent_uid = 1;
+
+ optional int32 isolated_uid = 2;
+
+ // We expect an isolated uid to be removed before if it's used for another parent uid.
+ enum Event {
+ REMOVED = 0;
+ CREATED = 1;
+ }
+ optional Event event = 3;
+}
+
+/*
+ * Logs the reception of an incoming network packet causing the main system to wake up for
+ * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd
+ * and processed by WakeupController.cpp.
+ */
+message PacketWakeupOccurred {
+ // The uid owning the socket into which the packet was delivered, or -1 if the packet was
+ // delivered nowhere.
+ optional int32 uid = 1;
+ // The interface name on which the packet was received.
+ optional string iface = 2;
+ // The ethertype value of the packet.
+ optional int32 ethertype = 3;
+ // String representation of the destination MAC address of the packet.
+ optional string destination_hardware_address = 4;
+ // String representation of the source address of the packet if this was an IP packet.
+ optional string source_ip = 5;
+ // String representation of the destination address of the packet if this was an IP packet.
+ optional string destination_ip = 6;
+ // The value of the protocol field if this was an IPv4 packet or the value of the Next Header
+ // field if this was an IPv6 packet. The range of possible values is the same for both IP
+ // families.
+ optional int32 ip_next_header = 7;
+ // The source port if this was a TCP or UDP packet.
+ optional int32 source_port = 8;
+ // The destination port if this was a TCP or UDP packet.
+ optional int32 destination_port = 9;
+}
+
+/*
+ * Logs the memory stats for an app on startup.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message AppStartMemoryStateCaptured {
+ // The uid if available. -1 means not available.
+ optional int32 uid = 1;
+
+ // The process name.
+ optional string process_name = 2;
+
+ // The activity name.
+ optional string activity_name = 3;
+
+ // # of page-faults
+ optional int64 pgfault = 4;
+
+ // # of major page-faults
+ optional int64 pgmajfault = 5;
+
+ // RSS
+ optional int64 rss_in_bytes = 6;
+
+ // CACHE
+ optional int64 cache_in_bytes = 7;
+
+ // SWAP
+ optional int64 swap_in_bytes = 8;
+}
+
+/*
+ * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries
+ * for LMK event.
+ * Logged from:
+ * system/core/lmkd/lmkd.c
+ */
+message LmkStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ START = 1;
+ STOP = 2;
+ }
+ optional State state = 1;
+}
+
+/*
+ * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure.
+ * Logged from:
+ * system/core/lmkd/lmkd.c
+ */
+message LmkKillOccurred {
+ // The uid if available. -1 means not available.
+ optional int32 uid = 1;
+
+ // The process name.
+ optional string process_name = 2;
+
+ // oom adj score.
+ optional int32 oom_score = 3;
+
+ // # of page-faults
+ optional int64 pgfault = 4;
+
+ // # of major page-faults
+ optional int64 pgmajfault = 5;
+
+ // RSS
+ optional int64 rss_in_bytes = 6;
+
+ // CACHE
+ optional int64 cache_in_bytes = 7;
+
+ // SWAP
+ optional int64 swap_in_bytes = 8;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Pulled atoms below this line //
+//////////////////////////////////////////////////////////////////////
+
/**
* Pulls bytes transferred via wifi (Sum of foreground and background usage).
*
@@ -1114,37 +1295,15 @@
* hardware/interfaces/power/1.1/types.hal
*/
message SubsystemSleepState {
- // Name should be in the format of XXX.YYY where XXX is subsystem name,
- // YYY is corresponding voter name.
- // If there are no voters, the format should just be XXX (with no dot).
- // XXX and YYY should not contain a "." in it.
- optional string name = 1;
+ // Subsystem name
+ optional string subsystem_name = 1;
+ // For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty.
+ // For SubsystemLowPowerStats (hal 1.1), this is the sleep state name.
+ optional string subname = 2;
// The number of times it entered, or voted for entering the sleep state
- optional uint64 count = 2;
+ optional uint64 count = 3;
// The length of time spent in, or spent voting for, the sleep state
- optional uint64 timeMs = 3;
-}
-
-/**
- * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky
- * behavior in its own uid. However, the metrics of these isolated uid's almost always should be
- * attributed back to the parent (host) uid. One example is Chrome.
- *
- * Logged from:
- * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
- */
-message IsolatedUidChanged {
- // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid.
- optional int32 parent_uid = 1;
-
- optional int32 isolated_uid = 2;
-
- // We expect an isolated uid to be removed before if it's used for another parent uid.
- enum Event {
- REMOVED = 0;
- CREATED = 1;
- }
- optional Event event = 3;
+ optional uint64 timeMs = 4;
}
/**
@@ -1181,35 +1340,6 @@
optional uint64 time_ms = 3;
}
-/*
- * Logs the reception of an incoming network packet causing the main system to wake up for
- * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd
- * and processed by WakeupController.cpp.
- */
-message PacketWakeupOccurred {
- // The uid owning the socket into which the packet was delivered, or -1 if the packet was
- // delivered nowhere.
- optional int32 uid = 1;
- // The interface name on which the packet was received.
- optional string iface = 2;
- // The ethertype value of the packet.
- optional int32 ethertype = 3;
- // String representation of the destination MAC address of the packet.
- optional string destination_hardware_address = 4;
- // String representation of the source address of the packet if this was an IP packet.
- optional string source_ip = 5;
- // String representation of the destination address of the packet if this was an IP packet.
- optional string destination_ip = 6;
- // The value of the protocol field if this was an IPv4 packet or the value of the Next Header
- // field if this was an IPv6 packet. The range of possible values is the same for both IP
- // families.
- optional int32 ip_next_header = 7;
- // The source port if this was a TCP or UDP packet.
- optional int32 source_port = 8;
- // The destination port if this was a TCP or UDP packet.
- optional int32 destination_port = 9;
-}
-
/**
* Pulls Wifi Controller Activity Energy Info
*/
@@ -1283,37 +1413,6 @@
}
/*
- * Logs the memory stats for an app on startup.
- * Logged from:
- * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- */
-message AppStartMemoryStateCaptured {
- // The uid if available. -1 means not available.
- optional int32 uid = 1;
-
- // The process name.
- optional string process_name = 2;
-
- // The activity name.
- optional string activity_name = 3;
-
- // # of page-faults
- optional int64 pgfault = 4;
-
- // # of major page-faults
- optional int64 pgmajfault = 5;
-
- // RSS
- optional int64 rss_in_bytes = 6;
-
- // CACHE
- optional int64 cache_in_bytes = 7;
-
- // SWAP
- optional int64 swap_in_bytes = 8;
-}
-
-/*
* Logs the memory stats for a process.
*/
message ProcessMemoryState {
@@ -1343,52 +1442,6 @@
}
/*
- * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries
- * for LMK event.
- * Logged from:
- * system/core/lmkd/lmkd.c
- */
-message LmkStateChanged {
- enum State {
- UNKNOWN = 0;
- START = 1;
- STOP = 2;
- }
- optional State state = 1;
-}
-
-/*
- * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure.
- * Logged from:
- * system/core/lmkd/lmkd.c
- */
-message LmkKillOccurred {
- // The uid if available. -1 means not available.
- optional int32 uid = 1;
-
- // The process name.
- optional string process_name = 2;
-
- // oom adj score.
- optional int32 oom_score = 3;
-
- // # of page-faults
- optional int64 pgfault = 4;
-
- // # of major page-faults
- optional int64 pgmajfault = 5;
-
- // RSS
- optional int64 rss_in_bytes = 6;
-
- // CACHE
- optional int64 cache_in_bytes = 7;
-
- // SWAP
- optional int64 swap_in_bytes = 8;
-}
-
-/*
* Elapsed real time from SystemClock.
*/
message SystemElapsedRealtime {
@@ -1463,3 +1516,4 @@
message FullBatteryCapacity {
optional int32 capacity_uAh = 1;
}
+
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 61eeee3..06ff603 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -75,8 +75,8 @@
}
}
-void ConfigManager::SetConfigReceiver(const ConfigKey& key, const string& pkg, const string& cls) {
- mConfigReceivers[key] = pair<string, string>(pkg, cls);
+void ConfigManager::SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender) {
+ mConfigReceivers[key] = intentSender;
}
void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
@@ -159,10 +159,10 @@
return ret;
}
-const pair<string, string> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
+const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
auto it = mConfigReceivers.find(key);
if (it == mConfigReceivers.end()) {
- return pair<string,string>();
+ return nullptr;
} else {
return it->second;
}
@@ -175,8 +175,7 @@
fprintf(out, " %6d %lld\n", key.GetUid(), (long long)key.GetId());
auto receiverIt = mConfigReceivers.find(key);
if (receiverIt != mConfigReceivers.end()) {
- fprintf(out, " -> received by %s, %s\n", receiverIt->second.first.c_str(),
- receiverIt->second.second.c_str());
+ fprintf(out, " -> received by PendingIntent as binder\n");
}
}
}
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index ad666bc..a2b2a0c 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -16,6 +16,7 @@
#pragma once
+#include "binder/IBinder.h"
#include "config/ConfigKey.h"
#include "config/ConfigListener.h"
@@ -68,12 +69,12 @@
/**
* Sets the broadcast receiver for a configuration key.
*/
- void SetConfigReceiver(const ConfigKey& key, const std::string& pkg, const std::string& cls);
+ void SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender);
/**
* Returns the package name and class name representing the broadcast receiver for this config.
*/
- const std::pair<std::string, std::string> GetConfigReceiver(const ConfigKey& key) const;
+ const sp<android::IBinder> GetConfigReceiver(const ConfigKey& key) const;
/**
* Returns all config keys registered.
@@ -124,10 +125,10 @@
std::set<ConfigKey> mConfigs;
/**
- * Each config key can be subscribed by up to one receiver, specified as the package name and
- * class name.
+ * Each config key can be subscribed by up to one receiver, specified as IBinder from
+ * PendingIntent.
*/
- std::map<ConfigKey, std::pair<std::string, std::string>> mConfigReceivers;
+ std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
/**
* The ConfigListeners that will be told about changes.
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
index a751273..d0d2f93 100644
--- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include <fstream>
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
index e7ea4b9..d9aeb46 100644
--- a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include <fstream>
diff --git a/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp b/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp
index 7a2d199..0e126e7 100644
--- a/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp
+++ b/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include <fstream>
diff --git a/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp b/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp
index 7426e74..7684ed4 100644
--- a/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp
+++ b/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include <fstream>
diff --git a/cmds/statsd/src/external/Perfetto.cpp b/cmds/statsd/src/external/Perfetto.cpp
index 1d8a968..b09d373 100644
--- a/cmds/statsd/src/external/Perfetto.cpp
+++ b/cmds/statsd/src/external/Perfetto.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
@@ -36,7 +37,7 @@
namespace statsd {
bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config) {
- ALOGD("Starting trace collection through perfetto");
+ VLOG("Starting trace collection through perfetto");
if (!config.has_trace_config()) {
ALOGE("The perfetto trace config is empty, aborting");
@@ -118,7 +119,7 @@
return false;
}
- ALOGD("CollectPerfettoTraceAndUploadToDropbox() succeeded");
+ VLOG("CollectPerfettoTraceAndUploadToDropbox() succeeded");
return true;
}
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
index b955f1c..8210c8d 100644
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
+++ b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true
+#define DEBUG false
#include "Log.h"
#include <android/os/IStatsCompanionService.h>
@@ -64,7 +64,7 @@
std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + kLogMsgHeaderSize);
data->push_back(make_shared<LogEvent>(tmp));
}
- ALOGD("StatsCompanionServicePuller::pull succeeded for %d", mTagId);
+ VLOG("StatsCompanionServicePuller::pull succeeded for %d", mTagId);
return true;
} else {
ALOGW("statsCompanion not found!");
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
index da14434..fc0ad7c 100644
--- a/cmds/statsd/src/external/StatsPuller.cpp
+++ b/cmds/statsd/src/external/StatsPuller.cpp
@@ -19,6 +19,7 @@
#include "StatsPuller.h"
#include "guardrail/StatsdStats.h"
+#include "puller_util.h"
namespace android {
namespace os {
@@ -26,6 +27,9 @@
using std::lock_guard;
+sp<UidMap> StatsPuller::mUidMap = nullptr;
+void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; }
+
// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
StatsPuller::StatsPuller(const int tagId)
: mTagId(tagId) {
@@ -54,14 +58,30 @@
mLastPullTimeSec = curTime;
bool ret = PullInternal(&mCachedData);
if (ret) {
- (*data) = mCachedData;
+ mergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId);
+ (*data) = mCachedData;
}
return ret;
}
-void StatsPuller::ClearCache() {
+int StatsPuller::ForceClearCache() {
+ return clearCache();
+}
+
+int StatsPuller::clearCache() {
lock_guard<std::mutex> lock(mLock);
+ int ret = mCachedData.size();
mCachedData.clear();
+ mLastPullTimeSec = 0;
+ return ret;
+}
+
+int StatsPuller::ClearCacheIfNecessary(long timestampSec) {
+ if (timestampSec - mLastPullTimeSec > mCoolDownSec) {
+ return clearCache();
+ } else {
+ return 0;
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index bc7c45f..82a8611 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -18,11 +18,14 @@
#include <android/os/StatsLogEventWrapper.h>
#include <utils/String16.h>
+#include <utils/RefBase.h>
#include <mutex>
#include <vector>
+#include "packages/UidMap.h"
-#include "logd/LogEvent.h"
#include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
+#include "puller_util.h"
using android::os::StatsLogEventWrapper;
@@ -30,7 +33,7 @@
namespace os {
namespace statsd {
-class StatsPuller {
+class StatsPuller : public virtual RefBase {
public:
StatsPuller(const int tagId);
@@ -38,9 +41,15 @@
bool Pull(std::vector<std::shared_ptr<LogEvent>>* data);
- void ClearCache();
+ // Clear cache immediately
+ int ForceClearCache();
-protected:
+ // Clear cache if elapsed time is more than cooldown time
+ int ClearCacheIfNecessary(long timestampSec);
+
+ static void SetUidMap(const sp<UidMap>& uidMap);
+
+ protected:
// The atom tag id this puller pulls
const int mTagId;
@@ -61,6 +70,10 @@
std::vector<std::shared_ptr<LogEvent>> mCachedData;
long mLastPullTimeSec;
+
+ int clearCache();
+
+ static sp<UidMap> mUidMap;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 4826d96..0dee342 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -54,8 +54,12 @@
mPullerManager.SetTimeBaseSec(timeBaseSec);
}
- void ClearPullerCache() {
- mPullerManager.ClearPullerCache();
+ int ForceClearPullerCache() {
+ return mPullerManager.ForceClearPullerCache();
+ }
+
+ int ClearPullerCacheIfNecessary(long timestampSec) {
+ return mPullerManager.ClearPullerCacheIfNecessary(timestampSec);
}
private:
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index 71b0abe..4c676a7 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true
+#define DEBUG false
#include "Log.h"
#include <android/os/IStatsCompanionService.h>
@@ -24,6 +24,9 @@
#include "CpuTimePerUidFreqPuller.h"
#include "CpuTimePerUidPuller.h"
#include "ResourceHealthManagerPuller.h"
+#include "KernelUidCpuActiveTimeReader.h"
+#include "KernelUidCpuClusterTimeReader.h"
+#include "SubsystemSleepStatePuller.h"
#include "StatsCompanionServicePuller.h"
#include "StatsPullerManagerImpl.h"
#include "StatsService.h"
@@ -44,62 +47,89 @@
namespace os {
namespace statsd {
+const std::map<int, PullAtomInfo> StatsPullerManagerImpl::kAllPullAtomInfo = {
+ // wifi_bytes_transfer
+ {android::util::WIFI_BYTES_TRANSFER,
+ {{2, 3, 4, 5},
+ {},
+ 1,
+ new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
+ // wifi_bytes_transfer_by_fg_bg
+ {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
+ {{3, 4, 5, 6},
+ {2},
+ 1,
+ new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
+ // mobile_bytes_transfer
+ {android::util::MOBILE_BYTES_TRANSFER,
+ {{2, 3, 4, 5},
+ {},
+ 1,
+ new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
+ // mobile_bytes_transfer_by_fg_bg
+ {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
+ {{3, 4, 5, 6},
+ {2},
+ 1,
+ new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
+ // bluetooth_bytes_transfer
+ {android::util::BLUETOOTH_BYTES_TRANSFER,
+ {{2, 3}, {}, 1, new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
+ // kernel_wakelock
+ {android::util::KERNEL_WAKELOCK,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+ // subsystem_sleep_state
+ {android::util::SUBSYSTEM_SLEEP_STATE, {{}, {}, 1, new SubsystemSleepStatePuller()}},
+ // cpu_time_per_freq
+ {android::util::CPU_TIME_PER_FREQ,
+ {{3}, {2}, 1, new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
+ // cpu_time_per_uid
+ {android::util::CPU_TIME_PER_UID, {{2, 3}, {}, 1, new CpuTimePerUidPuller()}},
+ // cpu_time_per_uid_freq
+ {android::util::CPU_TIME_PER_UID_FREQ, {{3}, {2}, 1, new CpuTimePerUidFreqPuller()}},
+ // wifi_activity_energy_info
+ {android::util::WIFI_ACTIVITY_ENERGY_INFO,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_ENERGY_INFO)}},
+ // modem_activity_info
+ {android::util::MODEM_ACTIVITY_INFO,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
+ // bluetooth_activity_info
+ {android::util::BLUETOOTH_ACTIVITY_INFO,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
+ // system_elapsed_realtime
+ {android::util::SYSTEM_ELAPSED_REALTIME,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
+ // system_uptime
+ {android::util::SYSTEM_UPTIME,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+ // cpu_active_time
+ {android::util::CPU_ACTIVE_TIME, {{3}, {2}, 1, new KernelUidCpuActiveTimeReader()}},
+ // cpu_cluster_time
+ {android::util::CPU_CLUSTER_TIME, {{3}, {2}, 1, new KernelUidCpuClusterTimeReader()}},
+ // disk_space
+ {android::util::DISK_SPACE,
+ {{}, {}, 1, new StatsCompanionServicePuller(android::util::DISK_SPACE)}},
+ // remaining_battery_capacity
+ {android::util::REMAINING_BATTERY_CAPACITY,
+ {{}, {}, 1, new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
+ // full_battery_capacity
+ {android::util::FULL_BATTERY_CAPACITY,
+ {{}, {}, 1, new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}};
+
StatsPullerManagerImpl::StatsPullerManagerImpl()
: mCurrentPullingInterval(LONG_MAX) {
- mPullers.insert({android::util::KERNEL_WAKELOCK,
- make_shared<StatsCompanionServicePuller>(android::util::KERNEL_WAKELOCK)});
- mPullers.insert({android::util::WIFI_BYTES_TRANSFER,
- make_shared<StatsCompanionServicePuller>(android::util::WIFI_BYTES_TRANSFER)});
- mPullers.insert(
- {android::util::MOBILE_BYTES_TRANSFER,
- make_shared<StatsCompanionServicePuller>(android::util::MOBILE_BYTES_TRANSFER)});
- mPullers.insert({android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
- make_shared<StatsCompanionServicePuller>(
- android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)});
- mPullers.insert({android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
- make_shared<StatsCompanionServicePuller>(
- android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)});
- mPullers.insert(
- {android::util::SUBSYSTEM_SLEEP_STATE,
- make_shared<SubsystemSleepStatePuller>()});
- mPullers.insert({android::util::CPU_TIME_PER_FREQ, make_shared<StatsCompanionServicePuller>(android::util::CPU_TIME_PER_FREQ)});
- mPullers.insert({android::util::CPU_TIME_PER_UID, make_shared<CpuTimePerUidPuller>()});
- mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, make_shared<CpuTimePerUidFreqPuller>()});
- mPullers.insert(
- {android::util::SYSTEM_ELAPSED_REALTIME,
- make_shared<StatsCompanionServicePuller>(android::util::SYSTEM_ELAPSED_REALTIME)});
- mPullers.insert({android::util::SYSTEM_UPTIME,
- make_shared<StatsCompanionServicePuller>(android::util::SYSTEM_UPTIME)});
- mPullers.insert({android::util::DISK_SPACE,
- make_shared<StatsCompanionServicePuller>(android::util::DISK_SPACE)});
- mPullers.insert(
- {android::util::BLUETOOTH_ACTIVITY_INFO,
- make_shared<StatsCompanionServicePuller>(android::util::BLUETOOTH_ACTIVITY_INFO)});
-
- mPullers.insert(
- {android::util::BLUETOOTH_BYTES_TRANSFER,
- make_shared<StatsCompanionServicePuller>(android::util::BLUETOOTH_BYTES_TRANSFER)});
- mPullers.insert(
- {android::util::WIFI_ACTIVITY_ENERGY_INFO,
- make_shared<StatsCompanionServicePuller>(android::util::WIFI_ACTIVITY_ENERGY_INFO)});
- mPullers.insert({android::util::MODEM_ACTIVITY_INFO,
- make_shared<StatsCompanionServicePuller>(android::util::MODEM_ACTIVITY_INFO)});
- mPullers.insert({android::util::REMAINING_BATTERY_CAPACITY,
- make_shared<ResourceHealthManagerPuller>(android::util::REMAINING_BATTERY_CAPACITY)});
- mPullers.insert({android::util::FULL_BATTERY_CAPACITY,
- make_shared<ResourceHealthManagerPuller>(android::util::FULL_BATTERY_CAPACITY)});
mStatsCompanionService = StatsService::getStatsCompanionService();
}
bool StatsPullerManagerImpl::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
- if (DEBUG) ALOGD("Initiating pulling %d", tagId);
+ VLOG("Initiating pulling %d", tagId);
- if (mPullers.find(tagId) != mPullers.end()) {
- bool ret = mPullers.find(tagId)->second->Pull(data);
- ALOGD("pulled %d items", (int)data->size());
+ if (kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end()) {
+ bool ret = kAllPullAtomInfo.find(tagId)->second.puller->Pull(data);
+ VLOG("pulled %d items", (int)data->size());
return ret;
} else {
- ALOGD("Unknown tagId %d", tagId);
+ VLOG("Unknown tagId %d", tagId);
return false; // Return early since we don't know what to pull.
}
}
@@ -110,7 +140,7 @@
}
bool StatsPullerManagerImpl::PullerForMatcherExists(int tagId) const {
- return mPullers.find(tagId) != mPullers.end();
+ return kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end();
}
void StatsPullerManagerImpl::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver,
@@ -199,10 +229,20 @@
}
}
-void StatsPullerManagerImpl::ClearPullerCache() {
- for (auto puller : mPullers) {
- puller.second->ClearCache();
+int StatsPullerManagerImpl::ForceClearPullerCache() {
+ int totalCleared = 0;
+ for (const auto& pulledAtom : kAllPullAtomInfo) {
+ totalCleared += pulledAtom.second.puller->ForceClearCache();
}
+ return totalCleared;
+}
+
+int StatsPullerManagerImpl::ClearPullerCacheIfNecessary(long timestampSec) {
+ int totalCleared = 0;
+ for (const auto& pulledAtom : kAllPullAtomInfo) {
+ totalCleared += pulledAtom.second.puller->ClearCacheIfNecessary(timestampSec);
+ }
+ return totalCleared;
}
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h
index fba3ade..76a4c14 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.h
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h
@@ -32,6 +32,20 @@
namespace os {
namespace statsd {
+typedef struct {
+ // The field numbers of the fields that need to be summed when merging
+ // isolated uid with host uid.
+ std::vector<int> additiveFields;
+ // The field numbers of the fields that can't be merged when merging
+ // data belong to isolated uid and host uid.
+ std::vector<int> nonAdditiveFields;
+ // How long should the puller wait before doing an actual pull again. Default
+ // 1 sec. Set this to 0 if this is handled elsewhere.
+ long coolDownSec = 1;
+ // The actual puller
+ sp<StatsPuller> puller;
+} PullAtomInfo;
+
class StatsPullerManagerImpl : public virtual RefBase {
public:
static StatsPullerManagerImpl& GetInstance();
@@ -49,9 +63,13 @@
void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;};
- void ClearPullerCache();
+ int ForceClearPullerCache();
-private:
+ int ClearPullerCacheIfNecessary(long timestampSec);
+
+ const static std::map<int, PullAtomInfo> kAllPullAtomInfo;
+
+ private:
StatsPullerManagerImpl();
// use this to update alarm
@@ -59,9 +77,6 @@
sp<IStatsCompanionService> get_stats_companion_service();
- // mapping from simple matcher tagId to puller
- std::map<int, std::shared_ptr<StatsPuller>> mPullers;
-
typedef struct {
// pull_interval_sec : last_pull_time_sec
std::pair<uint64_t, uint64_t> timeInfo;
@@ -79,8 +94,6 @@
// bucket size. All pulled metrics start pulling based on this time, so that they can be
// correctly attributed to the correct buckets.
long mTimeBaseSec;
-
- LogEvent parse_pulled_data(String16 data);
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
index 6d12e25..65a1df0e 100644
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
+++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include <android/hardware/power/1.0/IPower.h>
@@ -99,6 +99,7 @@
auto statePtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE,
timestamp);
statePtr->write(state.name);
+ statePtr->write("");
statePtr->write(state.totalTransitions);
statePtr->write(state.residencyInMsecSinceBoot);
statePtr->init();
@@ -110,7 +111,8 @@
for (auto voter : state.voters) {
auto voterPtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE,
timestamp);
- voterPtr->write((std::string)state.name + "." + (std::string)voter.name);
+ voterPtr->write(state.name);
+ voterPtr->write(voter.name);
voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot);
voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
voterPtr->init();
@@ -144,7 +146,8 @@
subsystem.states[j];
auto subsystemStatePtr = make_shared<LogEvent>(
android::util::SUBSYSTEM_SLEEP_STATE, timestamp);
- subsystemStatePtr->write((std::string)subsystem.name + "." + (std::string)state.name);
+ subsystemStatePtr->write(subsystem.name);
+ subsystemStatePtr->write(state.name);
subsystemStatePtr->write(state.totalTransitions);
subsystemStatePtr->write(state.residencyInMsecSinceBoot);
subsystemStatePtr->init();
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
new file mode 100644
index 0000000..7cfc1d48
--- /dev/null
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG false // STOPSHIP if true
+#include "Log.h"
+
+#include "StatsPullerManagerImpl.h"
+#include "field_util.h"
+#include "puller_util.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::shared_ptr;
+using std::vector;
+
+DimensionsValue* getFieldValue(shared_ptr<LogEvent> event, int tagId, int fieldNum) {
+ Field field;
+ buildSimpleAtomField(tagId, fieldNum, &field);
+ return event->findFieldValueOrNull(field);
+}
+
+bool shouldMerge(shared_ptr<LogEvent>& lhs, shared_ptr<LogEvent>& rhs,
+ const vector<int>& nonAdditiveFields, int tagId) {
+ for (int f : nonAdditiveFields) {
+ DimensionsValue* lValue = getFieldValue(lhs, tagId, f);
+ DimensionsValue* rValue = getFieldValue(rhs, tagId, f);
+ if (!compareDimensionsValue(*lValue, *rValue)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// merge rhs to lhs
+void mergeEvent(shared_ptr<LogEvent>& lhs, shared_ptr<LogEvent>& rhs,
+ const vector<int>& additiveFields, int tagId) {
+ for (int f : additiveFields) {
+ DimensionsValue* lValue = getFieldValue(lhs, tagId, f);
+ DimensionsValue* rValue = getFieldValue(rhs, tagId, f);
+ if (lValue->has_value_int()) {
+ lValue->set_value_int(lValue->value_int() + rValue->value_int());
+ } else if (lValue->has_value_long()) {
+ lValue->set_value_long(lValue->value_long() + rValue->value_long());
+ }
+ }
+}
+
+// process all data and merge isolated with host if necessary
+void mergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data,
+ const sp<UidMap>& uidMap, int tagId) {
+ if (StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId) ==
+ StatsPullerManagerImpl::kAllPullAtomInfo.end()) {
+ VLOG("Unknown pull atom id %d", tagId);
+ return;
+ }
+ if (android::util::kAtomsWithUidField.find(tagId) ==
+ android::util::kAtomsWithUidField.end()) {
+ VLOG("No uid to merge for atom %d", tagId);
+ return;
+ }
+ const vector<int>& additiveFields =
+ StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)
+ ->second.additiveFields;
+ const vector<int>& nonAdditiveFields =
+ StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)
+ ->second.nonAdditiveFields;
+
+ // map of host uid to isolated uid data index in the original vector.
+ // because of non additive fields, there could be multiple of them that can't
+ // be merged into one
+ map<int, vector<int>> hostToIsolated;
+ // map of host uid to their position in the original vector
+ map<int, vector<int>> hostPosition;
+ vector<int> isolatedUidPos;
+ // all uids in the original vector
+ vector<int> allUids;
+ for (size_t i = 0; i < data.size(); i++) {
+ // uid field is always first primitive filed, if present
+ DimensionsValue* uidField = getFieldValue(data[i], tagId, 1);
+ if (!uidField) {
+ VLOG("Bad data for %d, %s", tagId, data[i]->ToString().c_str());
+ return;
+ }
+ int uid = uidField->value_int();
+ allUids.push_back(uid);
+ const int hostUid = uidMap->getHostUidOrSelf(uid);
+ if (hostUid != uid) {
+ uidField->set_value_int(hostUid);
+ hostToIsolated[hostUid].push_back(i);
+ isolatedUidPos.push_back(i);
+ }
+ }
+ vector<shared_ptr<LogEvent>> mergedData;
+ for (size_t i = 0; i < allUids.size(); i++) {
+ if (hostToIsolated.find(allUids[i]) != hostToIsolated.end()) {
+ hostPosition[allUids[i]].push_back(i);
+ } else if (std::find(isolatedUidPos.begin(), isolatedUidPos.end(), i) != isolatedUidPos.end()) {
+ continue;
+ } else {
+ mergedData.push_back(data[i]);
+ }
+ }
+ for (auto iter = hostToIsolated.begin(); iter != hostToIsolated.end();
+ iter++) {
+ int uid = iter->first;
+ vector<int>& isolated = hostToIsolated[uid];
+ vector<int> toBeMerged;
+ toBeMerged.insert(toBeMerged.begin(), isolated.begin(), isolated.end());
+ if (hostPosition.find(uid) != hostPosition.end()) {
+ vector<int>& host = hostPosition[uid];
+ toBeMerged.insert(toBeMerged.end(), host.begin(), host.end());
+ }
+ vector<bool> used(toBeMerged.size());
+ for (size_t i = 0; i < toBeMerged.size(); i++) {
+ if (used[i] == true) {
+ continue;
+ }
+ for (size_t j = i + 1; j < toBeMerged.size(); j++) {
+ shared_ptr<LogEvent>& lhs = data[toBeMerged[i]];
+ shared_ptr<LogEvent>& rhs = data[toBeMerged[j]];
+ if (shouldMerge(lhs, rhs, nonAdditiveFields, tagId)) {
+ mergeEvent(lhs, rhs, additiveFields, tagId);
+ used[j] = true;
+ }
+ }
+ }
+ for (size_t i = 0; i < toBeMerged.size(); i++) {
+ if (used[i] == false) {
+ mergedData.push_back(data[i]);
+ }
+ }
+ }
+ data.clear();
+ data = mergedData;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/puller_util.h b/cmds/statsd/src/external/puller_util.h
new file mode 100644
index 0000000..70d5321
--- /dev/null
+++ b/cmds/statsd/src/external/puller_util.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+#include "HashableDimensionKey.h"
+#include "StatsPuller.h"
+#include "logd/LogEvent.h"
+#include "packages/UidMap.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void mergeIsolatedUidsToHostUid(std::vector<std::shared_ptr<LogEvent>>& data,
+ const sp<UidMap>& uidMap, int tagId);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
index e1947c4..01c7587 100644
--- a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include <sstream>
@@ -63,7 +63,7 @@
size_t count;
if (info == nullptr || overallSize == 0 || infoSize == 0 ||
(count = overallSize / infoSize) == 0) {
- ALOGD("no malloc info, libc.debug.malloc.program property should be set");
+ VLOG("no malloc info, libc.debug.malloc.program property should be set");
return std::string();
}
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 77f5456..06c5b00 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "StatsdStats.h"
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 1f4bfa6..f254327 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -85,6 +85,9 @@
// Maximum size of all files that can be written to stats directory on disk.
static const int kMaxFileSize = 50 * 1024 * 1024;
+ // How long to try to clear puller cache from last time
+ static const long kPullerCacheClearIntervalSec = 1;
+
/**
* Report a new config has been received and report the static stats about the config.
*
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 1dcd853..e1ab5d5 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "logd/LogEvent.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -294,6 +294,28 @@
}
}
+int LogEvent::GetInt(size_t key, status_t* err) const {
+ DimensionsValue value;
+ if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
+ *err = BAD_INDEX;
+ return 0;
+ }
+ const DimensionsValue* leafValue = getSingleLeafValue(&value);
+ switch (leafValue->value_case()) {
+ case DimensionsValue::ValueCase::kValueInt:
+ return leafValue->value_int();
+ case DimensionsValue::ValueCase::kValueLong:
+ case DimensionsValue::ValueCase::kValueBool:
+ case DimensionsValue::ValueCase::kValueFloat:
+ case DimensionsValue::ValueCase::kValueTuple:
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+ *err = BAD_TYPE;
+ return 0;
+ }
+ }
+}
+
const char* LogEvent::GetString(size_t key, status_t* err) const {
DimensionsValue value;
if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index eb2c008..d521e09 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -76,6 +76,7 @@
* Returns BAD_TYPE if the index is available but the data is the wrong type.
*/
int64_t GetLong(size_t key, status_t* err) const;
+ int GetInt(size_t key, status_t* err) const;
const char* GetString(size_t key, status_t* err) const;
bool GetBool(size_t key, status_t* err) const;
float GetFloat(size_t key, status_t* err) const;
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index ae4df3e..5a042b6 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -45,8 +45,6 @@
// for StatsLogReport
const int FIELD_ID_ID = 1;
-const int FIELD_ID_START_REPORT_NANOS = 2;
-const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_COUNT_METRICS = 5;
// for CountMetricDataWrapper
const int FIELD_ID_DATA = 1;
@@ -97,7 +95,6 @@
void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
flushIfNeededLocked(dumpTimeNs);
report->set_metric_id(mMetricId);
- report->set_start_report_nanos(mStartTimeNs);
auto count_metrics = report->mutable_count_metrics();
for (const auto& counter : mPastBuckets) {
@@ -123,7 +120,6 @@
}
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
VLOG("metric %lld dump report now...",(long long)mMetricId);
@@ -167,10 +163,8 @@
}
protoOutput->end(protoToken);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
@@ -214,13 +208,11 @@
}
auto it = mCurrentSlicedCounter->find(eventKey);
-
if (it == mCurrentSlicedCounter->end()) {
// ===========GuardRail==============
if (hitGuardRailLocked(eventKey)) {
return;
}
-
// create a counter for the new key
(*mCurrentSlicedCounter)[eventKey] = 1;
} else {
@@ -228,10 +220,14 @@
auto& count = it->second;
count++;
}
-
for (auto& tracker : mAnomalyTrackers) {
+ int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second;
+ auto prev = mCurrentFullCounters->find(eventKey);
+ if (prev != mCurrentFullCounters->end()) {
+ countWholeBucket += prev->second;
+ }
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
- mCurrentSlicedCounter->find(eventKey)->second);
+ countWholeBucket);
}
VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(),
@@ -241,33 +237,65 @@
// When a new matched event comes in, we check if event falls into the current
// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
- if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+ if (eventTimeNs < currentBucketEndTimeNs) {
return;
}
+ flushCurrentBucketLocked(eventTimeNs);
+ // Setup the bucket start time and number.
+ uint64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+void CountMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
+ uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
CountBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ if (eventTimeNs < fullBucketEndTimeNs) {
+ info.mBucketEndNs = eventTimeNs;
+ } else {
+ info.mBucketEndNs = fullBucketEndTimeNs;
+ }
info.mBucketNum = mCurrentBucketNum;
for (const auto& counter : *mCurrentSlicedCounter) {
info.mCount = counter.second;
auto& bucketList = mPastBuckets[counter.first];
bucketList.push_back(info);
- VLOG("metric %lld, dump key value: %s -> %lld",
- (long long)mMetricId, counter.first.c_str(), (long long)counter.second);
+ VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, counter.first.c_str(),
+ (long long)counter.second);
}
- for (auto& tracker : mAnomalyTrackers) {
- tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
+ // If we have finished a full bucket, then send this to anomaly tracker.
+ if (eventTimeNs > fullBucketEndTimeNs) {
+ // Accumulate partial buckets with current value and then send to anomaly tracker.
+ if (mCurrentFullCounters->size() > 0) {
+ for (const auto& keyValuePair : *mCurrentSlicedCounter) {
+ (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
+ }
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum);
+ }
+ mCurrentFullCounters = std::make_shared<DimToValMap>();
+ } else {
+ // Skip aggregating the partial buckets since there's no previous partial bucket.
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
+ }
+ }
+ } else {
+ // Accumulate partial bucket.
+ for (const auto& keyValuePair : *mCurrentSlicedCounter) {
+ (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
+ }
}
- // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
+ // Only resets the counters, but doesn't setup the times nor numbers.
+ // (Do not clear since the old one is still referenced in mAnomalyTrackers).
mCurrentSlicedCounter = std::make_shared<DimToValMap>();
- uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
- VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
- (long long)mCurrentBucketStartTimeNs);
}
// Rough estimate of CountMetricProducer buffer stored. This number will be
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 8659d47..b06c77b 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -71,14 +71,20 @@
void dumpStatesLocked(FILE* out, bool verbose) const override{};
// Util function to flush the old packet.
- void flushIfNeededLocked(const uint64_t& newEventTime);
+ void flushIfNeededLocked(const uint64_t& newEventTime) override;
+
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets;
- // The current bucket.
+ // The current bucket (may be a partial bucket).
std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
+ // The sum of previous partial buckets in the current full bucket (excluding the current
+ // partial bucket). This is only updated while flushing the current bucket.
+ std::shared_ptr<DimToValMap> mCurrentFullCounters = std::make_shared<DimToValMap>();
+
static const size_t kBucketSize = sizeof(CountBucket{});
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
@@ -87,6 +93,8 @@
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
+ FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
+ FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index efbdae1..65cbc4a 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -44,8 +44,6 @@
// for StatsLogReport
const int FIELD_ID_ID = 1;
-const int FIELD_ID_START_REPORT_NANOS = 2;
-const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_DURATION_METRICS = 6;
// for DurationMetricDataWrapper
const int FIELD_ID_DATA = 1;
@@ -121,13 +119,13 @@
case DurationMetric_AggregationType_SUM:
return make_unique<OringDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mDimensionsInCondition, mNested,
- mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+ mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
+ mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mDimensionsInCondition, mNested,
- mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+ mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
+ mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
}
}
@@ -179,7 +177,6 @@
void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
flushIfNeededLocked(dumpTimeNs);
report->set_metric_id(mMetricId);
- report->set_start_report_nanos(mStartTimeNs);
auto duration_metrics = report->mutable_duration_metrics();
for (const auto& pair : mPastBuckets) {
@@ -205,7 +202,6 @@
}
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS);
VLOG("metric %lld dump report now...", (long long)mMetricId);
@@ -250,19 +246,19 @@
}
protoOutput->end(protoToken);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
}
-void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
+ if (currentBucketEndTimeNs > eventTimeNs) {
return;
}
VLOG("flushing...........");
for (auto it = mCurrentSlicedDurationTrackerMap.begin();
it != mCurrentSlicedDurationTrackerMap.end();) {
- if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) {
+ if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
VLOG("erase bucket for key %s", it->first.c_str());
it = mCurrentSlicedDurationTrackerMap.erase(it);
} else {
@@ -270,11 +266,23 @@
}
}
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+ int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
}
+void DurationMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
+ for (auto it = mCurrentSlicedDurationTrackerMap.begin();
+ it != mCurrentSlicedDurationTrackerMap.end();) {
+ if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s", it->first.c_str());
+ it = mCurrentSlicedDurationTrackerMap.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
if (mCurrentSlicedDurationTrackerMap.size() == 0) {
return;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 152e570..a496016 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -73,6 +73,8 @@
// Util function to flush the old packet.
void flushIfNeededLocked(const uint64_t& eventTime);
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
+
const DurationMetric_AggregationType mAggregationType;
// Index of the SimpleAtomMatcher which defines the start.
@@ -112,6 +114,10 @@
FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
+ FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade);
+ FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket);
+ FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade);
+ FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket);
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
};
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 820d591..0578e06 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -42,8 +42,6 @@
// for StatsLogReport
const int FIELD_ID_ID = 1;
-const int FIELD_ID_START_REPORT_NANOS = 2;
-const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_EVENT_METRICS = 4;
// for EventMetricDataWrapper
const int FIELD_ID_DATA = 1;
@@ -106,8 +104,6 @@
return;
}
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
size_t bufferSize = mProto->size();
VLOG("metric %lld dump report now... proto size: %zu ",
@@ -118,7 +114,6 @@
reinterpret_cast<char*>(buffer.get()->data()), buffer.get()->size());
startNewProtoOutputStreamLocked();
- mStartTimeNs = dumpTimeNs;
}
void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index d6cb189..62ee6ef 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -45,8 +45,6 @@
// for StatsLogReport
const int FIELD_ID_ID = 1;
-const int FIELD_ID_START_REPORT_NANOS = 2;
-const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_GAUGE_METRICS = 8;
// for GaugeMetricDataWrapper
const int FIELD_ID_DATA = 1;
@@ -134,7 +132,6 @@
}
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
for (const auto& pair : mPastBuckets) {
@@ -188,13 +185,22 @@
protoOutput->end(wrapperToken);
}
protoOutput->end(protoToken);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
+void GaugeMetricProducer::pullLocked() {
+ vector<std::shared_ptr<LogEvent>> allData;
+ if (!mStatsPullerManager->Pull(mPullTagId, &allData)) {
+ ALOGE("Stats puller failed for tag: %d", mPullTagId);
+ return;
+ }
+ for (const auto& data : allData) {
+ onMatchedLogEventLocked(0, *data);
+ }
+}
+
void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet,
const uint64_t eventTime) {
VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
@@ -326,7 +332,6 @@
}
void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() {
- mCurrentSlicedBucketForAnomaly->clear();
status_t err = NO_ERROR;
for (const auto& slice : *mCurrentSlicedBucket) {
if (slice.second.empty() || slice.second.front().mFields->empty()) {
@@ -349,42 +354,57 @@
// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
// the GaugeMetricProducer while holding the lock.
void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
- if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
+ if (eventTimeNs < currentBucketEndTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
(long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
return;
}
+ flushCurrentBucketLocked(eventTimeNs);
+
+ // Adjusts the bucket start and end times.
+ int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+void GaugeMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
+ uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
GaugeBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ if (eventTimeNs < fullBucketEndTimeNs) {
+ info.mBucketEndNs = eventTimeNs;
+ } else {
+ info.mBucketEndNs = fullBucketEndTimeNs;
+ }
info.mBucketNum = mCurrentBucketNum;
for (const auto& slice : *mCurrentSlicedBucket) {
info.mGaugeAtoms = slice.second;
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
- VLOG("gauge metric %lld, dump key value: %s",
- (long long)mMetricId, slice.first.c_str());
+ VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId, slice.first.c_str());
}
- // Reset counters
+ // If we have anomaly trackers, we need to update the partial bucket values.
if (mAnomalyTrackers.size() > 0) {
updateCurrentSlicedBucketForAnomaly();
- for (auto& tracker : mAnomalyTrackers) {
- tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum);
+
+ if (eventTimeNs > fullBucketEndTimeNs) {
+ // This is known to be a full bucket, so send this data to the anomaly tracker.
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum);
+ }
+ mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
}
}
- mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
-
- // Adjusts the bucket start time
- int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
- VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
- (long long)mCurrentBucketStartTimeNs);
}
size_t GaugeMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 86d0ccd..d5d34be 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -62,6 +62,22 @@
// Handles when the pulled data arrives.
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+ // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (eventTimeNs > getCurrentBucketEndTimeNs()) {
+ // Flush full buckets on the normal path up to the latest bucket boundary.
+ flushIfNeededLocked(eventTimeNs);
+ }
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ if (mPullTagId != -1) {
+ pullLocked();
+ }
+ };
+
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -91,7 +107,11 @@
void dumpStatesLocked(FILE* out, bool verbose) const override{};
// Util function to flush the old packet.
- void flushIfNeededLocked(const uint64_t& eventTime);
+ void flushIfNeededLocked(const uint64_t& eventTime) override;
+
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
+
+ void pullLocked();
std::shared_ptr<StatsPullerManager> mStatsPullerManager;
// tagId for pulled data. -1 if this is not pulled
@@ -101,13 +121,15 @@
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
- // The current bucket.
+ // The current partial bucket.
std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket;
- // The current bucket for anomaly detection.
+ // The current full bucket for anomaly detection. This is updated to the latest value seen for
+ // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket).
std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
- // Translate Atom based bucket to single numeric value bucket for anomaly
+ // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map
+ // for each slice with the latest value.
void updateCurrentSlicedBucketForAnomaly();
// Whitelist of fields to report. Empty means all are reported.
@@ -125,6 +147,8 @@
FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition);
+ FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade);
+ FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade);
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
};
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 3b1498f..542dd8a 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -53,15 +53,32 @@
virtual ~MetricProducer(){};
- void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
+ /**
+ * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we
+ * first call the standard flushing code to flush up to the latest full bucket. Then we call
+ * the flush again when the end timestamp is forced to be now, and then after flushing, update
+ * the start timestamp to be now.
+ */
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (eventTimeNs > getCurrentBucketEndTimeNs()) {
+ // Flush full buckets on the normal path up to the latest bucket boundary.
+ flushIfNeededLocked(eventTimeNs);
+ }
+ // Now flush a partial bucket.
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ // Don't update the current bucket number so that the anomaly tracker knows this bucket
+ // is a partial bucket and can merge it with the previous bucket.
+ };
+
+ void notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, const int uid) override{
// TODO: Implement me.
};
- void notifyAppRemoved(const string& apk, const int uid) override{
- // TODO: Implement me.
- };
-
- void onUidMapReceived() override{
+ void onUidMapReceived(const uint64_t& eventTimeNs) override{
// TODO: Implement me.
};
@@ -87,11 +104,12 @@
};
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
+ // This method clears all the past buckets.
void onDumpReport(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) {
std::lock_guard<std::mutex> lock(mMutex);
return onDumpReportLocked(dumpTimeNs, protoOutput);
}
-
+ // This method does not clear the past buckets.
void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) {
std::lock_guard<std::mutex> lock(mMutex);
return onDumpReportLocked(dumpTimeNs, report);
@@ -136,15 +154,43 @@
virtual size_t byteSizeLocked() const = 0;
virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
+ /**
+ * Flushes the current bucket if the eventTime is after the current bucket's end time.
+ */
+ virtual void flushIfNeededLocked(const uint64_t& eventTime){};
+
+ /**
+ * For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
+ * we need to be able to flush the current buckets on demand (ie, end the current bucket and
+ * start new bucket). If this function is called when eventTimeNs is greater than the current
+ * bucket's end timestamp, than we flush up to the end of the latest full bucket; otherwise,
+ * we assume that we want to flush a partial bucket. The bucket start timestamp and bucket
+ * number are not changed by this function. This method should only be called by
+ * flushIfNeededLocked or the app upgrade handler; the caller MUST update the bucket timestamp
+ * and bucket number as needed.
+ */
+ virtual void flushCurrentBucketLocked(const uint64_t& eventTimeNs){};
+
+ // Convenience to compute the current bucket's end time, which is always aligned with the
+ // start time of the metric.
+ uint64_t getCurrentBucketEndTimeNs() {
+ return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+ }
+
const int64_t mMetricId;
const ConfigKey mConfigKey;
- // The start time for the current in memory metrics data.
+ // The time when this metric producer was first created. The end time for the current bucket
+ // can be computed from this based on mCurrentBucketNum.
uint64_t mStartTimeNs;
+ // Start time may not be aligned with the start of statsd if there is an app upgrade in the
+ // middle of a bucket.
uint64_t mCurrentBucketStartTimeNs;
+ // Used by anomaly detector to track which bucket we are in. This is not sent with the produced
+ // report.
uint64_t mCurrentBucketNum;
int64_t mBucketSizeNs;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 417145c..6c21b05 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "MetricsManager.h"
#include "statslog.h"
@@ -28,6 +28,8 @@
#include "stats_util.h"
#include <log/logprint.h>
+#include <private/android_filesystem_config.h>
+#include <utils/SystemClock.h>
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_MESSAGE;
@@ -47,7 +49,7 @@
MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
const long timeBaseSec, sp<UidMap> uidMap)
- : mConfigKey(key), mUidMap(uidMap), mStatsdUid(getStatsdUid()) {
+ : mConfigKey(key), mUidMap(uidMap), mLastReportTimeNs(0) {
mConfigValid =
initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
@@ -59,9 +61,9 @@
// mConfigValid = false;
// ALOGE("Log source white list is empty! This config won't get any data.");
- mAllowedUid.push_back(1000);
- mAllowedUid.push_back(0);
- mAllowedUid.push_back(mStatsdUid);
+ mAllowedUid.push_back(AID_ROOT);
+ mAllowedUid.push_back(AID_STATSD);
+ mAllowedUid.push_back(AID_SYSTEM);
mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
} else {
for (const auto& source : config.allowed_log_source()) {
@@ -120,7 +122,8 @@
return mConfigValid;
}
-void MetricsManager::notifyAppUpgrade(const string& apk, const int uid, const int64_t version) {
+void MetricsManager::notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) {
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
return;
@@ -130,7 +133,8 @@
initLogSourceWhiteList();
}
-void MetricsManager::notifyAppRemoved(const string& apk, const int uid) {
+void MetricsManager::notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk,
+ const int uid) {
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
return;
@@ -140,7 +144,7 @@
initLogSourceWhiteList();
}
-void MetricsManager::onUidMapReceived() {
+void MetricsManager::onUidMapReceived(const uint64_t& eventTimeNs) {
if (mAllowedPkg.size() == 0) {
return;
}
@@ -181,6 +185,7 @@
protoOutput->end(token);
}
}
+ mLastReportTimeNs = ::android::elapsedRealtimeNano();
VLOG("=========================Metric Reports End==========================");
}
@@ -198,7 +203,7 @@
// unless that caller is statsd itself (statsd is allowed to spoof uids).
long appHookUid = event.GetLong(event.size()-2, &err);
int32_t loggerUid = event.GetUid();
- if (err != NO_ERROR || (loggerUid != appHookUid && loggerUid != mStatsdUid)) {
+ if (err != NO_ERROR || (loggerUid != appHookUid && loggerUid != AID_STATSD)) {
VLOG("AppHook has invalid uid: claimed %ld but caller is %d", appHookUid, loggerUid);
return;
}
@@ -333,16 +338,6 @@
return totalSize;
}
-int32_t MetricsManager::getStatsdUid() {
- auto suit = UidMap::sAidToUidMapping.find("AID_STATSD");
- if (suit != UidMap::sAidToUidMapping.end()) {
- return suit->second;
- } else {
- ALOGE("Statsd failed to find its own uid!");
- return -1;
- }
-}
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a1220f9..2b30f44 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -51,11 +51,12 @@
void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
- void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override;
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override;
- void notifyAppRemoved(const string& apk, const int uid) override;
+ void notifyAppRemoved(const uint64_t& eventTimeNs, const string& apk, const int uid) override;
- void onUidMapReceived() override;
+ void onUidMapReceived(const uint64_t& eventTimeNs) override;
bool shouldAddUidMapListener() const {
return !mAllowedPkg.empty();
@@ -63,6 +64,11 @@
void dumpStates(FILE* out, bool verbose);
+ // Returns the elapsed realtime when this metric manager last reported metrics.
+ uint64_t getLastReportTimeNs() {
+ return mLastReportTimeNs;
+ };
+
// Config source owner can call onDumpReport() to get all the metrics collected.
virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput);
virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report);
@@ -75,11 +81,10 @@
sp<UidMap> mUidMap;
- // The uid of statsd.
- const int32_t mStatsdUid;
-
bool mConfigValid = false;
+ uint64_t mLastReportTimeNs;
+
// The uid log sources from StatsdConfig.
std::vector<int32_t> mAllowedUid;
@@ -139,9 +144,6 @@
void initLogSourceWhiteList();
- // Fetches the uid of statsd from UidMap.
- static int32_t getStatsdUid();
-
// The metrics that don't need to be uploaded or even reported.
std::set<int64_t> mNoReportMetricIds;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index c9cc7bb..7b1944c 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -48,8 +48,6 @@
// for StatsLogReport
const int FIELD_ID_ID = 1;
-const int FIELD_ID_START_REPORT_NANOS = 2;
-const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_VALUE_METRICS = 7;
// for ValueMetricDataWrapper
const int FIELD_ID_DATA = 1;
@@ -122,7 +120,6 @@
void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
flushIfNeededLocked(dumpTimeNs);
report->set_metric_id(mMetricId);
- report->set_start_report_nanos(mStartTimeNs);
auto value_metrics = report->mutable_value_metrics();
for (const auto& pair : mPastBuckets) {
ValueMetricData* metricData = value_metrics->add_data();
@@ -147,7 +144,6 @@
return;
}
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
for (const auto& pair : mPastBuckets) {
@@ -186,11 +182,9 @@
protoOutput->end(wrapperToken);
}
protoOutput->end(protoToken);
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
VLOG("metric %lld dump report now...", (long long)mMetricId);
mPastBuckets.clear();
- mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
@@ -320,8 +314,13 @@
interval.sum += value;
}
+ long wholeBucketVal = interval.sum;
+ auto prev = mCurrentFullBucket.find(eventKey);
+ if (prev != mCurrentFullBucket.end()) {
+ wholeBucketVal += prev->second;
+ }
for (auto& tracker : mAnomalyTrackers) {
- tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, interval.sum);
+ tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, wholeBucketVal);
}
}
@@ -333,16 +332,39 @@
}
void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+ uint64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
+ if (currentBucketEndTimeNs > eventTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
- (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+ (long long)(currentBucketEndTimeNs));
return;
}
+
+ flushCurrentBucketLocked(eventTimeNs);
+
+ int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+
+ if (numBucketsForward > 1) {
+ VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
+ }
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+void ValueMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
(int)mCurrentSlicedBucket.size());
+ uint64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
+
ValueBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ if (eventTimeNs < fullBucketEndTimeNs) {
+ info.mBucketEndNs = eventTimeNs;
+ } else {
+ info.mBucketEndNs = fullBucketEndTimeNs;
+ }
info.mBucketNum = mCurrentBucketNum;
int tainted = 0;
@@ -352,27 +374,42 @@
// it will auto create new vector of ValuebucketInfo if the key is not found.
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
-
- for (auto& tracker : mAnomalyTrackers) {
- if (tracker != nullptr) {
- tracker->addPastBucket(slice.first, info.mValue, info.mBucketNum);
- }
- }
}
VLOG("%d tainted pairs in the bucket", tainted);
+ if (eventTimeNs > fullBucketEndTimeNs) { // If full bucket, send to anomaly tracker.
+ // Accumulate partial buckets with current value and then send to anomaly tracker.
+ if (mCurrentFullBucket.size() > 0) {
+ for (const auto& slice : mCurrentSlicedBucket) {
+ mCurrentFullBucket[slice.first] += slice.second.sum;
+ }
+ for (const auto& slice : mCurrentFullBucket) {
+ for (auto& tracker : mAnomalyTrackers) {
+ if (tracker != nullptr) {
+ tracker->addPastBucket(slice.first, slice.second, mCurrentBucketNum);
+ }
+ }
+ }
+ mCurrentFullBucket.clear();
+ } else {
+ // Skip aggregating the partial buckets since there's no previous partial bucket.
+ for (const auto& slice : mCurrentSlicedBucket) {
+ for (auto& tracker : mAnomalyTrackers) {
+ if (tracker != nullptr) {
+ tracker->addPastBucket(slice.first, slice.second.sum, mCurrentBucketNum);
+ }
+ }
+ }
+ }
+ } else {
+ // Accumulate partial bucket.
+ for (const auto& slice : mCurrentSlicedBucket) {
+ mCurrentFullBucket[slice.first] += slice.second.sum;
+ }
+ }
+
// Reset counters
mCurrentSlicedBucket.clear();
-
- int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
-
- if (numBucketsForward > 1) {
- VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
- }
- VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
- (long long)mCurrentBucketStartTimeNs);
}
size_t ValueMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 121ec7d..bf5b7df 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -47,6 +47,39 @@
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+ // ValueMetric needs special logic if it's a pulled atom.
+ void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
+ const int64_t version) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (mPullTagId != -1) {
+ vector<shared_ptr<LogEvent>> allData;
+ mStatsPullerManager->Pull(mPullTagId, &allData);
+ if (allData.size() == 0) {
+ // This shouldn't happen since this valuemetric is not useful now.
+ }
+
+ // Pretend the pulled data occurs right before the app upgrade event.
+ mCondition = false;
+ for (const auto& data : allData) {
+ data->setTimestampNs(eventTimeNs - 1);
+ onMatchedLogEventLocked(0, *data);
+ }
+
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+
+ mCondition = true;
+ for (const auto& data : allData) {
+ data->setTimestampNs(eventTimeNs);
+ onMatchedLogEventLocked(0, *data);
+ }
+ } else { // For pushed value metric, we simply flush and reset the current bucket start.
+ flushCurrentBucketLocked(eventTimeNs);
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ }
+ };
+
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -70,7 +103,9 @@
void dumpStatesLocked(FILE* out, bool verbose) const override{};
// Util function to flush the old packet.
- void flushIfNeededLocked(const uint64_t& eventTime);
+ void flushIfNeededLocked(const uint64_t& eventTime) override;
+
+ void flushCurrentBucketLocked(const uint64_t& eventTimeNs) override;
const FieldMatcher mValueField;
@@ -101,6 +136,8 @@
std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket;
+ std::unordered_map<MetricDimensionKey, long> mCurrentFullBucket;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
@@ -114,6 +151,8 @@
FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
+ FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade);
+ FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade);
FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition);
FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection);
};
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 45735a8..356a81c 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -63,7 +63,8 @@
DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
sp<ConditionWizard> wizard, int conditionIndex,
const FieldMatcher& dimensionInCondition, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs,
+ uint64_t bucketSizeNs, bool conditionSliced,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: mConfigKey(key),
mTrackerId(id),
@@ -75,7 +76,9 @@
mNested(nesting),
mCurrentBucketStartTimeNs(currentBucketStartNs),
mDuration(0),
- mCurrentBucketNum(0),
+ mDurationFullBucket(0),
+ mCurrentBucketNum(currentBucketNum),
+ mStartTimeNs(startTimeNs),
mConditionSliced(conditionSliced),
mAnomalyTrackers(anomalyTrackers){};
@@ -98,6 +101,12 @@
uint64_t timestampNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
+ // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from
+ // an app upgrade, we assume that we're trying to form a partial bucket.
+ virtual bool flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
+
// Predict the anomaly timestamp given the current status.
virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const uint64_t currentTimestamp) const = 0;
@@ -153,6 +162,13 @@
}
}
}
+
+ // Convenience to compute the current bucket's end time, which is always aligned with the
+ // start time of the metric.
+ uint64_t getCurrentBucketEndTimeNs() {
+ return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+ }
+
// A reference to the DurationMetricProducer's config key.
const ConfigKey& mConfigKey;
@@ -172,10 +188,14 @@
uint64_t mCurrentBucketStartTimeNs;
- int64_t mDuration; // current recorded duration result
+ int64_t mDuration; // current recorded duration result (for partial bucket)
+
+ int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket.
uint64_t mCurrentBucketNum;
+ const uint64_t mStartTimeNs;
+
const bool mConditionSliced;
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index db7dea4..c3bafc6 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -28,11 +28,13 @@
const MetricDimensionKey& eventKey,
sp<ConditionWizard> wizard, int conditionIndex,
const FieldMatcher& dimensionInCondition, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs,
bool conditionSliced,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
- currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) {
+ currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
+ conditionSliced, anomalyTrackers) {
}
unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) {
@@ -102,7 +104,6 @@
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
bool forceStop) {
- declareAnomalyIfAlarmExpired(eventTime);
VLOG("MaxDuration: key %s stop", key.c_str());
if (mInfos.find(key) == mInfos.end()) {
// we didn't see a start event before. do nothing.
@@ -122,7 +123,7 @@
VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
(long long)duration.lastStartTime, (long long)eventTime,
(long long)durationTime);
- duration.lastDuration = duration.lastDuration + durationTime;
+ duration.lastDuration += durationTime;
VLOG(" record duration: %lld ", (long long)duration.lastDuration);
}
break;
@@ -138,7 +139,6 @@
if (duration.lastDuration > mDuration) {
mDuration = duration.lastDuration;
- detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
VLOG("Max: new max duration: %lld", (long long)mDuration);
}
// Once an atom duration ends, we erase it. Next time, if we see another atom event with the
@@ -158,88 +158,67 @@
}
}
-bool MaxDurationTracker::flushIfNeeded(
- uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
- return false;
- }
-
+bool MaxDurationTracker::flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
VLOG("MaxDurationTracker flushing.....");
// adjust the bucket start time
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ int numBucketsForward = 0;
+ uint64_t fullBucketEnd = getCurrentBucketEndTimeNs();
+ uint64_t currentBucketEndTimeNs;
+ if (eventTimeNs >= fullBucketEnd) {
+ numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs;
+ currentBucketEndTimeNs = fullBucketEnd;
+ } else {
+ // This must be a partial bucket.
+ currentBucketEndTimeNs = eventTimeNs;
+ }
- uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-
- DurationBucket info;
- info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = endTime;
- info.mBucketNum = mCurrentBucketNum;
-
- uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
- mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-
- bool hasOnGoingStartedEvent = false; // a kStarted event last across bucket boundaries.
bool hasPendingEvent =
false; // has either a kStarted or kPaused event across bucket boundaries
- // meaning we need to carry them over to the new bucket.
+ // meaning we need to carry them over to the new bucket.
for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
int64_t finalDuration = it->second.lastDuration;
- if (it->second.state == kStarted) {
- // the event is still on-going, duration needs to be updated.
- // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
- // duration between lastStartTime and bucketEnd.
- int64_t durationTime = endTime - it->second.lastStartTime;
-
- finalDuration += durationTime;
- VLOG(" unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
- // if the event is still on-going, we need to fill the buckets between prev_bucket and
- // now_bucket. |prev_bucket|...|..|...|now_bucket|
- hasOnGoingStartedEvent = true;
- }
-
- if (finalDuration > mDuration) {
- mDuration = finalDuration;
- }
-
if (it->second.state == DurationState::kStopped) {
// No need to keep buckets for events that were stopped before.
mInfos.erase(it);
} else {
hasPendingEvent = true;
- // for kPaused, and kStarted event, we will keep track of them, and reset the start time
- // and duration.
- it->second.lastStartTime = mCurrentBucketStartTimeNs;
- it->second.lastDuration = 0;
}
}
+ // mDuration is updated in noteStop to the maximum duration that ended in the current bucket.
if (mDuration != 0) {
+ DurationBucket info;
+ info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ info.mBucketEndNs = currentBucketEndTimeNs;
+ info.mBucketNum = mCurrentBucketNum;
info.mDuration = mDuration;
(*output)[mEventKey].push_back(info);
- addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
VLOG(" final duration for last bucket: %lld", (long long)mDuration);
}
- mDuration = 0;
- if (hasOnGoingStartedEvent) {
- for (int i = 1; i < numBucketsForward; i++) {
- DurationBucket info;
- info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
- info.mBucketEndNs = endTime + mBucketSizeNs * i;
- info.mBucketNum = mCurrentBucketNum + i;
- info.mDuration = mBucketSizeNs;
- (*output)[mEventKey].push_back(info);
- addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
- VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
- }
+ if (numBucketsForward > 0) {
+ mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ } else { // We must be forming a partial bucket.
+ mCurrentBucketStartTimeNs = eventTimeNs;
}
- mCurrentBucketNum += numBucketsForward;
+ mDuration = 0;
// If this tracker has no pending events, tell owner to remove.
return !hasPendingEvent;
}
+bool MaxDurationTracker::flushIfNeeded(
+ uint64_t eventTimeNs, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
+ if (eventTimeNs < getCurrentBucketEndTimeNs()) {
+ return false;
+ }
+ return flushCurrentBucket(eventTimeNs, output);
+}
+
void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
// Now for each of the on-going event, check if the condition has changed for them.
for (auto& pair : mInfos) {
@@ -267,7 +246,6 @@
void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
const uint64_t timestamp) {
- declareAnomalyIfAlarmExpired(timestamp);
auto it = mInfos.find(key);
if (it == mInfos.end()) {
return;
@@ -297,7 +275,6 @@
}
if (it->second.lastDuration > mDuration) {
mDuration = it->second.lastDuration;
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
}
}
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 4d32a06..fba4119 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,10 +28,11 @@
// they stop or bucket expires.
class MaxDurationTracker : public DurationTracker {
public:
- MaxDurationTracker(const ConfigKey& key, const int64_t& id,
- const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
- int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
+ MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
+ sp<ConditionWizard> wizard, int conditionIndex,
+ const FieldMatcher& dimensionInCondition, bool nesting,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
MaxDurationTracker(const MaxDurationTracker& tracker) = default;
@@ -47,6 +48,9 @@
bool flushIfNeeded(
uint64_t timestampNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
+ bool flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>*) override;
void onSlicedConditionMayChange(const uint64_t timestamp) override;
void onConditionChanged(bool condition, const uint64_t timestamp) override;
@@ -68,7 +72,6 @@
FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
FRIEND_TEST(MaxDurationTrackerTest, TestStopAll);
- FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 0feae36..85f7b7c 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,12 +26,13 @@
OringDurationTracker::OringDurationTracker(
const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs,
- uint64_t bucketSizeNs, bool conditionSliced,
+ sp<ConditionWizard> wizard, int conditionIndex, const FieldMatcher& dimensionInCondition,
+ bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
- currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers),
+ currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
+ conditionSliced, anomalyTrackers),
mStarted(),
mPaused() {
mLastStartTime = 0;
@@ -100,7 +101,7 @@
}
if (mStarted.empty()) {
mDuration += (timestamp - mLastStartTime);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
(long long)mDuration);
}
@@ -125,7 +126,7 @@
mDuration += (timestamp - mLastStartTime);
VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
(long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
}
stopAnomalyAlarm();
@@ -134,51 +135,83 @@
mConditionKeyMap.clear();
}
-bool OringDurationTracker::flushIfNeeded(
- uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
- if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
- return false;
- }
+bool OringDurationTracker::flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
VLOG("OringDurationTracker Flushing.............");
- // adjust the bucket start time
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- DurationBucket current_info;
- current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
- current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
- current_info.mBucketNum = mCurrentBucketNum;
+
+ // Note that we have to mimic the bucket time changes we do in the
+ // MetricProducer#notifyAppUpgrade.
+
+ int numBucketsForward = 0;
+ uint64_t fullBucketEnd = getCurrentBucketEndTimeNs();
+ uint64_t currentBucketEndTimeNs;
+
+ if (eventTimeNs >= fullBucketEnd) {
+ numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs;
+ currentBucketEndTimeNs = fullBucketEnd;
+ } else {
+ // This must be a partial bucket.
+ currentBucketEndTimeNs = eventTimeNs;
+ }
+
// Process the current bucket.
if (mStarted.size() > 0) {
- mDuration += (current_info.mBucketEndNs - mLastStartTime);
+ mDuration += (currentBucketEndTimeNs - mLastStartTime);
}
if (mDuration > 0) {
+ DurationBucket current_info;
+ current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ current_info.mBucketEndNs = currentBucketEndTimeNs;
+ current_info.mBucketNum = mCurrentBucketNum;
current_info.mDuration = mDuration;
(*output)[mEventKey].push_back(current_info);
- addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
+ mDurationFullBucket += mDuration;
+ if (eventTimeNs > fullBucketEnd) {
+ // End of full bucket, can send to anomaly tracker now.
+ addPastBucketToAnomalyTrackers(mDurationFullBucket, current_info.mBucketNum);
+ mDurationFullBucket = 0;
+ }
VLOG(" duration: %lld", (long long)current_info.mDuration);
}
if (mStarted.size() > 0) {
for (int i = 1; i < numBucketsForward; i++) {
DurationBucket info;
- info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
+ info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
info.mBucketNum = mCurrentBucketNum + i;
info.mDuration = mBucketSizeNs;
(*output)[mEventKey].push_back(info);
+ // Safe to send these buckets to anomaly tracker since they must be full buckets.
+ // If it's a partial bucket, numBucketsForward would be 0.
addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
}
}
- mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
- mCurrentBucketNum += numBucketsForward;
- mLastStartTime = mCurrentBucketStartTimeNs;
mDuration = 0;
+ if (numBucketsForward > 0) {
+ mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+ } else { // We must be forming a partial bucket.
+ mCurrentBucketStartTimeNs = eventTimeNs;
+ }
+ mLastStartTime = mCurrentBucketStartTimeNs;
+
// if all stopped, then tell owner it's safe to remove this tracker.
return mStarted.empty() && mPaused.empty();
}
+bool OringDurationTracker::flushIfNeeded(
+ uint64_t eventTimeNs, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
+ if (eventTimeNs < getCurrentBucketEndTimeNs()) {
+ return false;
+ }
+ return flushCurrentBucket(eventTimeNs, output);
+}
+
void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
declareAnomalyIfAlarmExpired(timestamp);
vector<pair<HashableDimensionKey, int>> startedToPaused;
@@ -211,7 +244,7 @@
mDuration += (timestamp - mLastStartTime);
VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
(long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
}
}
@@ -275,7 +308,7 @@
mDuration += (timestamp - mLastStartTime);
mPaused.insert(mStarted.begin(), mStarted.end());
mStarted.clear();
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
}
}
if (mStarted.empty()) {
@@ -298,7 +331,7 @@
// As we move into the future, old buckets get overwritten (so their old data is erased).
// Sum of past durations. Will change as we overwrite old buckets.
- int64_t pastNs = mDuration;
+ int64_t pastNs = mDuration + mDurationFullBucket;
pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
// How much of the threshold is still unaccounted after considering pastNs.
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 75b5a81..73e50e0 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -30,7 +30,8 @@
OringDurationTracker(const ConfigKey& key, const int64_t& id,
const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
+ uint64_t currentBucketStartNs, uint64_t currentBucketNum,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -46,6 +47,9 @@
void onSlicedConditionMayChange(const uint64_t timestamp) override;
void onConditionChanged(bool condition, const uint64_t timestamp) override;
+ bool flushCurrentBucket(
+ const uint64_t& eventTimeNs,
+ std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
bool flushIfNeeded(
uint64_t timestampNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index a0173ee..205c8e4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "../condition/CombinationConditionTracker.h"
@@ -541,7 +541,7 @@
ALOGE("initLogMatchingTrackers failed");
return false;
}
- ALOGD("initLogMatchingTrackers succeed...");
+ VLOG("initLogMatchingTrackers succeed...");
if (!initConditions(key, config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
trackerToConditionMap)) {
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index df29eb0..03cb364 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -28,13 +28,15 @@
public:
// Uid map will notify this listener that the app with apk name and uid has been upgraded to
// the specified version.
- virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int64_t version) = 0;
+ virtual void notifyAppUpgrade(const uint64_t& eventTimeNs, const std::string& apk,
+ const int uid, const int64_t version) = 0;
// Notify interested listeners that the given apk and uid combination no longer exits.
- virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
+ virtual void notifyAppRemoved(const uint64_t& eventTimeNs, const std::string& apk,
+ const int uid) = 0;
// Notify the listener that the UidMap snapshot is available.
- virtual void onUidMapReceived() = 0;
+ virtual void onUidMapReceived(const uint64_t& eventTimeNs) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 91279661..0d7b722 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "guardrail/StatsdStats.h"
@@ -119,7 +119,7 @@
for (auto weakPtr : broadcastList) {
auto strongPtr = weakPtr.promote();
if (strongPtr != NULL) {
- strongPtr->onUidMapReceived();
+ strongPtr->onUidMapReceived(timestamp);
}
}
}
@@ -166,7 +166,7 @@
for (auto weakPtr : broadcastList) {
auto strongPtr = weakPtr.promote();
if (strongPtr != NULL) {
- strongPtr->notifyAppUpgrade(appName, uid, versionCode);
+ strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
}
}
}
@@ -239,7 +239,7 @@
for (auto weakPtr : broadcastList) {
auto strongPtr = weakPtr.promote();
if (strongPtr != NULL) {
- strongPtr->notifyAppRemoved(app, uid);
+ strongPtr->notifyAppRemoved(timestamp, app, uid);
}
}
}
@@ -316,13 +316,13 @@
mLastUpdatePerConfigKey[key] = timestamp;
int64_t newMin = getMinimumTimestampNs();
- if (newMin > prevMin) {
+ if (newMin > prevMin) { // Delete anything possible now that the minimum has moved forward.
int64_t cutoff_nanos = newMin;
auto snapshots = mOutput.mutable_snapshots();
auto it_snapshots = snapshots->cbegin();
while (it_snapshots != snapshots->cend()) {
if (it_snapshots->timestamp_nanos() < cutoff_nanos) {
- // it_snapshots now points to the following element.
+ // it_snapshots points to the following element after erasing.
it_snapshots = snapshots->erase(it_snapshots);
} else {
++it_snapshots;
@@ -332,7 +332,7 @@
auto it_deltas = deltas->cbegin();
while (it_deltas != deltas->cend()) {
if (it_deltas->timestamp_nanos() < cutoff_nanos) {
- // it_deltas now points to the following element.
+ // it_snapshots points to the following element after erasing.
it_deltas = deltas->erase(it_deltas);
} else {
++it_deltas;
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 3304f6c..f1da452 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -91,7 +91,7 @@
void removeIsolatedUid(int isolatedUid, int parentUid);
// Returns the host uid if it exists. Otherwise, returns the same uid that was passed-in.
- int getHostUidOrSelf(int uid) const;
+ virtual int getHostUidOrSelf(int uid) const;
// Gets the output. If every config key has received the output, then the output is cleared.
UidMapping getOutput(const ConfigKey& key);
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index af21ca4..b56cffb 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -143,9 +143,7 @@
message StatsLogReport {
optional int64 metric_id = 1;
- optional int64 start_report_nanos = 2;
-
- optional int64 end_report_nanos = 3;
+ // Fields 2 and 3 are reserved.
message EventMetricDataWrapper {
repeated EventMetricData data = 1;
@@ -177,6 +175,10 @@
repeated StatsLogReport metrics = 1;
optional UidMapping uid_map = 2;
+
+ optional int64 last_report_nanos = 3;
+
+ optional int64 current_report_nanos = 4;
}
message ConfigMetricsReportList {
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 3eaf7a1..5a326a4 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -275,6 +275,12 @@
message IncidentdDetails {
repeated int32 section = 1;
+
+ enum Destination {
+ AUTOMATIC = 0;
+ EXPLICIT = 1;
+ }
+ optional Destination dest = 2;
}
message PerfettoDetails {
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 00d8658..23bd5561 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "android-base/stringprintf.h"
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
new file mode 100644
index 0000000..d9a8fc8
--- /dev/null
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG true
+#include "Log.h"
+
+#include "IncidentdReporter.h"
+#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert,
+ const ConfigKey& configKey) {
+ if (config.section_size() == 0) {
+ VLOG("The alert %lld contains zero section in config(%d,%lld)", alert.id(),
+ configKey.GetUid(), (long long) configKey.GetId());
+ return false;
+ }
+
+ IncidentReportArgs incidentReport;
+
+ android::os::IncidentHeaderProto header;
+ header.set_alert_id(alert.id());
+ header.mutable_config_key()->set_uid(configKey.GetUid());
+ header.mutable_config_key()->set_id(configKey.GetId());
+ incidentReport.addHeader(header);
+
+ for (int i = 0; i < config.section_size(); i++) {
+ incidentReport.addSection(config.section(i));
+ }
+
+ uint8_t dest;
+ switch (config.dest()) {
+ case IncidentdDetails_Destination_AUTOMATIC:
+ dest = android::os::DEST_AUTOMATIC;
+ break;
+ case IncidentdDetails_Destination_EXPLICIT:
+ dest = android::os::DEST_EXPLICIT;
+ break;
+ default:
+ dest = android::os::DEST_AUTOMATIC;
+ }
+ incidentReport.setDest(dest);
+
+ sp<IIncidentManager> service = interface_cast<IIncidentManager>(
+ defaultServiceManager()->getService(android::String16("incident")));
+ if (service == nullptr) {
+ ALOGW("Failed to fetch incident service.");
+ return false;
+ }
+ VLOG("Calling incidentd %p", service.get());
+ binder::Status s = service->reportIncident(incidentReport);
+ VLOG("Report incident status: %s", s.toString8().string());
+ return s.isOk();
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.h b/cmds/statsd/src/subscriber/IncidentdReporter.h
new file mode 100644
index 0000000..229ed77
--- /dev/null
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert, IncidentdDetails
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Calls incidentd to trigger an incident report and put in dropbox for uploading.
+ */
+bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert,
+ const ConfigKey& configKey);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index 1186a16..a99dbe8 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -40,9 +40,11 @@
auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
// The predicate is dimensioning by any attribution node and both by uid and tag.
- *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
- CreateAttributionUidAndTagDimensions(
+ FieldMatcher dimensions = CreateAttributionUidAndTagDimensions(
android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST});
+ // Also slice by the wakelock tag
+ dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock.
+ *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions;
*config.add_predicate() = holdingWakelockPredicate;
auto durationMetric = config.add_duration_metric();
@@ -58,133 +60,198 @@
return config;
}
-} // namespace
+std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
-TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
- ConfigKey cfgKey;
- for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
- auto config = CreateStatsdConfig(aggregationType);
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t bucketSizeNs =
+std::vector<AttributionNode> attributions2 = {CreateAttribution(111, "App2"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+/*
+Events:
+Screen off is met from (200ns,1 min+500ns].
+Acquire event for wl1 from 2ns to 1min+2ns
+Acquire event for wl2 from 1min-10ns to 2min-15ns
+*/
+void FeedEvents(StatsdConfig config, sp<StatsLogProcessor> processor) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
- EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
- EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
-
- auto screenTurnedOnEvent =
- CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
- bucketStartTimeNs + 1);
- auto screenTurnedOffEvent =
- CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
- bucketStartTimeNs + 200);
- auto screenTurnedOnEvent2 =
+ auto screenTurnedOnEvent = CreateScreenStateChangedEvent(
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1);
+ auto screenTurnedOffEvent = CreateScreenStateChangedEvent(
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200);
+ auto screenTurnedOnEvent2 =
CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
bucketStartTimeNs + bucketSizeNs + 500);
- std::vector<AttributionNode> attributions1 =
- {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ auto acquireEvent1 = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2);
+ auto releaseEvent1 =
+ CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
+ auto acquireEvent2 =
+ CreateAcquireWakelockEvent(attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
+ auto releaseEvent2 = CreateReleaseWakelockEvent(attributions2, "wl2",
+ bucketStartTimeNs + 2 * bucketSizeNs - 15);
- std::vector<AttributionNode> attributions2 =
- {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ std::vector<std::unique_ptr<LogEvent>> events;
- auto acquireEvent1 = CreateAcquireWakelockEvent(
- attributions1, "wl1", bucketStartTimeNs + 2);
- auto acquireEvent2 = CreateAcquireWakelockEvent(
- attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
+ events.push_back(std::move(screenTurnedOnEvent));
+ events.push_back(std::move(screenTurnedOffEvent));
+ events.push_back(std::move(screenTurnedOnEvent2));
+ events.push_back(std::move(acquireEvent1));
+ events.push_back(std::move(acquireEvent2));
+ events.push_back(std::move(releaseEvent1));
+ events.push_back(std::move(releaseEvent2));
- auto releaseEvent1 = CreateReleaseWakelockEvent(
- attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
- auto releaseEvent2 = CreateReleaseWakelockEvent(
- attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
+ sortLogEventsByTimestamp(&events);
-
- std::vector<std::unique_ptr<LogEvent>> events;
-
- events.push_back(std::move(screenTurnedOnEvent));
- events.push_back(std::move(screenTurnedOffEvent));
- events.push_back(std::move(screenTurnedOnEvent2));
- events.push_back(std::move(acquireEvent1));
- events.push_back(std::move(acquireEvent2));
- events.push_back(std::move(releaseEvent1));
- events.push_back(std::move(releaseEvent2));
-
- sortLogEventsByTimestamp(&events);
-
- for (const auto& event : events) {
- processor->OnLogEvent(event.get());
- }
-
- ConfigMetricsReportList reports;
- processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
- EXPECT_EQ(reports.reports_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics_size(), 1);
- // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
-
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
- // Validate dimension value.
- ValidateAttributionUidDimension(
- data.dimensions_in_what(),
- android::util::WAKELOCK_STATE_CHANGED, 111);
- // Validate bucket info.
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
- data = reports.reports(0).metrics(0).duration_metrics().data(0);
- // The wakelock holding interval starts from the screen off event and to the end of the 1st
- // bucket.
- EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200);
-
- reports.Clear();
- processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
- EXPECT_EQ(reports.reports_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
- // Dump the report after the end of 2nd bucket.
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
- data = reports.reports(0).metrics(0).duration_metrics().data(0);
- // Validate dimension value.
- ValidateAttributionUidDimension(
- data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111);
- // Two output buckets.
- // The wakelock holding interval in the 1st bucket starts from the screen off event and to
- // the end of the 1st bucket.
- EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(),
- bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200));
- // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
- // ends at the second screen on event.
- EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
-
- events.clear();
- events.push_back(CreateScreenStateChangedEvent(
- android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
- bucketStartTimeNs + 2 * bucketSizeNs + 90));
- events.push_back(CreateAcquireWakelockEvent(
- attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100));
- events.push_back(CreateReleaseWakelockEvent(
- attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100));
- sortLogEventsByTimestamp(&events);
- for (const auto& event : events) {
- processor->OnLogEvent(event.get());
- }
- reports.Clear();
- processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
- EXPECT_EQ(reports.reports_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
- EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
- data = reports.reports(0).metrics(0).duration_metrics().data(0);
- ValidateAttributionUidDimension(
- data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111);
- // The last wakelock holding spans 4 buckets.
- EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
- EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
- EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
- EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
}
}
+} // namespace
+
+TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration) {
+ ConfigKey cfgKey;
+ auto config = CreateStatsdConfig(DurationMetric::SUM);
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+ FeedEvents(config, processor);
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+
+ auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // Validate bucket info.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // The wakelock holding interval starts from the screen off event and to the end of the 1st
+ // bucket.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200);
+
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ // Dump the report after the end of 2nd bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // Two output buckets.
+ // The wakelock holding interval in the 1st bucket starts from the screen off event and to
+ // the end of the 1st bucket.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(),
+ bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200));
+ // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
+ // ends at the second screen on event.
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(
+ CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 2 * bucketSizeNs + 90));
+ events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 2 * bucketSizeNs + 100));
+ events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 5 * bucketSizeNs + 100));
+ sortLogEventsByTimestamp(&events);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The last wakelock holding spans 4 buckets.
+ EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
+ EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
+}
+
+TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration) {
+ ConfigKey cfgKey;
+ auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE);
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+ FeedEvents(config, processor);
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ // Nothing has ended in the first bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 0);
+
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ // Dump the report after the end of 2nd bucket. One dimension with one bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+ auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The max is acquire event for wl1 to screen off start.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(
+ CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 2 * bucketSizeNs + 90));
+ events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 2 * bucketSizeNs + 100));
+ events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3",
+ bucketStartTimeNs + 5 * bucketSizeNs + 100));
+ sortLogEventsByTimestamp(&events);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateAttributionUidDimension(data.dimensions_in_what(),
+ android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The last wakelock holding spans 4 buckets.
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_nanos(),
+ bucketStartTimeNs + 5 * bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).end_bucket_nanos(),
+ bucketStartTimeNs + 6 * bucketSizeNs);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
new file mode 100644
index 0000000..7d9c8a8
--- /dev/null
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -0,0 +1,269 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "external/puller_util.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+#include "../metrics/metrics_test_helper.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using namespace testing;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+using testing::Contains;
+/*
+ * Test merge isolated and host uid
+ */
+
+int uidAtomTagId = android::util::CPU_TIME_PER_UID_FREQ;
+int nonUidAtomTagId = android::util::SYSTEM_UPTIME;
+int timestamp = 1234;
+int isolatedUid = 30;
+int isolatedAdditiveData = 31;
+int isolatedNonAdditiveData = 32;
+int hostUid = 20;
+int hostAdditiveData = 21;
+int hostNonAdditiveData = 22;
+
+void extractIntoVector(vector<shared_ptr<LogEvent>> events,
+ vector<vector<int>>& ret) {
+ ret.clear();
+ status_t err;
+ for (const auto& event : events) {
+ vector<int> vec;
+ vec.push_back(event->GetInt(1, &err));
+ vec.push_back(event->GetInt(2, &err));
+ vec.push_back(event->GetInt(3, &err));
+ ret.push_back(vec);
+ }
+}
+
+TEST(puller_util, MergeNoDimension) {
+ vector<shared_ptr<LogEvent>> inputData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ // 30->22->31
+ event->write(isolatedUid);
+ event->write(hostNonAdditiveData);
+ event->write(isolatedAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 20->22->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(hostUid);
+ event->write(hostNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+ .WillRepeatedly(Return(hostUid));
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+ .WillRepeatedly(ReturnArg<0>());
+ mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+ vector<vector<int>> actual;
+ extractIntoVector(inputData, actual);
+ vector<int> expectedV1 = {20, 22, 52};
+ EXPECT_EQ(1, (int)actual.size());
+ EXPECT_THAT(actual, Contains(expectedV1));
+}
+
+TEST(puller_util, MergeWithDimension) {
+ vector<shared_ptr<LogEvent>> inputData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ // 30->32->31
+ event->write(isolatedUid);
+ event->write(isolatedNonAdditiveData);
+ event->write(isolatedAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 20->32->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(hostUid);
+ event->write(isolatedNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 20->22->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(hostUid);
+ event->write(hostNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+ .WillRepeatedly(Return(hostUid));
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+ .WillRepeatedly(ReturnArg<0>());
+ mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+ vector<vector<int>> actual;
+ extractIntoVector(inputData, actual);
+ vector<int> expectedV1 = {20, 22, 21};
+ vector<int> expectedV2 = {20, 32, 52};
+ EXPECT_EQ(2, (int)actual.size());
+ EXPECT_THAT(actual, Contains(expectedV1));
+ EXPECT_THAT(actual, Contains(expectedV2));
+}
+
+TEST(puller_util, NoMergeHostUidOnly) {
+ vector<shared_ptr<LogEvent>> inputData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ // 20->32->31
+ event->write(hostUid);
+ event->write(isolatedNonAdditiveData);
+ event->write(isolatedAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 20->22->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(hostUid);
+ event->write(hostNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+ .WillRepeatedly(Return(hostUid));
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+ .WillRepeatedly(ReturnArg<0>());
+ mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+ // 20->32->31
+ // 20->22->21
+ vector<vector<int>> actual;
+ extractIntoVector(inputData, actual);
+ vector<int> expectedV1 = {20, 32, 31};
+ vector<int> expectedV2 = {20, 22, 21};
+ EXPECT_EQ(2, (int)actual.size());
+ EXPECT_THAT(actual, Contains(expectedV1));
+ EXPECT_THAT(actual, Contains(expectedV2));
+}
+
+TEST(puller_util, IsolatedUidOnly) {
+ vector<shared_ptr<LogEvent>> inputData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ // 30->32->31
+ event->write(hostUid);
+ event->write(isolatedNonAdditiveData);
+ event->write(isolatedAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 30->22->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(hostUid);
+ event->write(hostNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid))
+ .WillRepeatedly(Return(hostUid));
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid)))
+ .WillRepeatedly(ReturnArg<0>());
+ mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+ // 20->32->31
+ // 20->22->21
+ vector<vector<int>> actual;
+ extractIntoVector(inputData, actual);
+ vector<int> expectedV1 = {20, 32, 31};
+ vector<int> expectedV2 = {20, 22, 21};
+ EXPECT_EQ(2, (int)actual.size());
+ EXPECT_THAT(actual, Contains(expectedV1));
+ EXPECT_THAT(actual, Contains(expectedV2));
+}
+
+TEST(puller_util, MultipleIsolatedUidToOneHostUid) {
+ vector<shared_ptr<LogEvent>> inputData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ // 30->32->31
+ event->write(isolatedUid);
+ event->write(isolatedNonAdditiveData);
+ event->write(isolatedAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 31->32->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(isolatedUid + 1);
+ event->write(isolatedNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ // 20->32->21
+ event = make_shared<LogEvent>(uidAtomTagId, timestamp);
+ event->write(hostUid);
+ event->write(isolatedNonAdditiveData);
+ event->write(hostAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(Return(hostUid));
+ mergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId);
+
+ vector<vector<int>> actual;
+ extractIntoVector(inputData, actual);
+ vector<int> expectedV1 = {20, 32, 73};
+ EXPECT_EQ(1, (int)actual.size());
+ EXPECT_THAT(actual, Contains(expectedV1));
+}
+
+TEST(puller_util, NoNeedToMerge) {
+ vector<shared_ptr<LogEvent>> inputData;
+ shared_ptr<LogEvent> event =
+ make_shared<LogEvent>(nonUidAtomTagId, timestamp);
+ // 32
+ event->write(isolatedNonAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ event = make_shared<LogEvent>(nonUidAtomTagId, timestamp);
+ // 22
+ event->write(hostNonAdditiveData);
+ event->init();
+ inputData.push_back(event);
+
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ mergeIsolatedUidsToHostUid(inputData, uidMap, nonUidAtomTagId);
+
+ EXPECT_EQ(2, (int)inputData.size());
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 50b3532..87a1079 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -191,6 +191,117 @@
EXPECT_EQ(1LL, bucketInfo.mCount);
}
+TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+
+ int tagId = 1;
+ int conditionTagId = 2;
+
+ CountMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ Alert alert;
+ alert.set_num_buckets(3);
+ alert.set_trigger_if_sum_gt(2);
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
+ bucketStartTimeNs);
+ sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert);
+ EXPECT_TRUE(anomalyTracker != nullptr);
+
+ // Bucket is flushed yet.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // App upgrade forces bucket flush.
+ // Check that there's a past bucket and the bucket end is not adjusted.
+ countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((long long)bucketStartTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ((long long)eventUpgradeTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ // Anomaly tracker only contains full buckets.
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ uint64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs();
+ // Next event occurs in same bucket as partial bucket created.
+ LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10);
+ event2.write("222"); // uid
+ event2.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Third event in following bucket.
+ LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10);
+ event3.write("333"); // uid
+ event3.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+ EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+}
+
+TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+
+ int tagId = 1;
+ int conditionTagId = 2;
+
+ CountMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
+ bucketStartTimeNs);
+
+ // Bucket is flushed yet.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+ // App upgrade forces bucket flush.
+ // Check that there's a past bucket and the bucket end is not adjusted.
+ countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((int64_t)bucketStartTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+ (uint64_t)countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+
+ // Next event occurs in same bucket as partial bucket created.
+ LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10);
+ event2.write("222"); // uid
+ event2.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+
+ // Third event in following bucket.
+ LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10);
+ event3.write("333"); // uid
+ event3.init();
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+ EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((int64_t)eventUpgradeTimeNs,
+ countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ (uint64_t)countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs);
+}
+
TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
Alert alert;
alert.set_id(11);
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index c9fe252..23e15f7 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -116,6 +116,231 @@
EXPECT_EQ(1ULL, buckets2[0].mDuration);
}
+TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
+ /**
+ * The duration starts from the first bucket, through the two partial buckets (10-70sec),
+ * another bucket, and ends at the beginning of the next full bucket.
+ * Expected buckets:
+ * - [10,25]: 14 secs
+ * - [25,70]: All 45 secs
+ * - [70,130]: All 60 secs
+ * - [130, 210]: Only 5 secs (event ended at 135sec)
+ */
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+ uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration);
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(3UL, buckets.size());
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
+ EXPECT_EQ(bucketSizeNs, buckets[2].mDuration);
+}
+
+TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
+ /**
+ * Expected buckets (start at 11s, upgrade at 75s, end at 135s):
+ * - [10,70]: 59 secs
+ * - [70,75]: 5 sec
+ * - [75,130]: 55 secs
+ */
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+ uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs);
+ EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(3UL, buckets.size());
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration);
+}
+
+TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1;
+ uint64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ // Setup metric with alert.
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+ Alert alert;
+ alert.set_num_buckets(3);
+ alert.set_trigger_if_sum_gt(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+ sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert);
+ EXPECT_TRUE(anomalyTracker != nullptr);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs,
+ (uint64_t)anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+}
+
+TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1;
+ uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
+ LogEvent event1(tagId, startTimeNs);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+
+ durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
+}
+
+TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
+ uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ uint64_t startTimeNs = bucketStartTimeNs + 1;
+ uint64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
+
+ int tagId = 1;
+
+ DurationMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_MINUTE);
+ metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
+ LogEvent event1(tagId, startTimeNs);
+ event1.write("111"); // uid
+ event1.init();
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ FieldMatcher dimensions;
+ DurationMetricProducer durationProducer(
+ kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
+
+ durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
+ EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ // Stop occurs in the same partial bucket as created for the app upgrade.
+ durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs));
+ EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+
+ durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+ std::vector<DurationBucket> buckets =
+ durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 58be5b0..470d4d0 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -44,6 +44,7 @@
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
+const uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
TEST(GaugeMetricProducerTest, TestNoCondition) {
GaugeMetric metric;
@@ -119,6 +120,143 @@
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
}
+TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
+ GaugeMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ metric.mutable_gauge_fields_filter()->set_include_all(true);
+
+ Alert alert;
+ alert.set_id(101);
+ alert.set_metric_id(metricId);
+ alert.set_trigger_if_sum_gt(25);
+ alert.set_num_buckets(100);
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+ -1 /* -1 means no pulling */, bucketStartTimeNs,
+ pullerManager);
+ sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
+ EXPECT_TRUE(anomalyTracker != nullptr);
+
+ shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event1->write(1);
+ event1->write(10);
+ event1->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+ EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
+
+ gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ // Partial buckets are not sent to anomaly tracker.
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Create an event in the same partial bucket.
+ shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC);
+ event2->write(1);
+ event2->write(10);
+ event2->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ // Partial buckets are not sent to anomaly tracker.
+ EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Next event should trigger creation of new bucket and send previous full bucket to anomaly
+ // tracker.
+ shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC);
+ event3->write(1);
+ event3->write(10);
+ event3->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+
+ // Next event should trigger creation of new bucket.
+ shared_ptr<LogEvent> event4 =
+ make_shared<LogEvent>(tagId, bucketStartTimeNs + 125 * NS_PER_SEC);
+ event4->write(1);
+ event4->write(10);
+ event4->init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
+ EXPECT_EQ(2UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
+}
+
+TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
+ GaugeMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+ gaugeFieldMatcher->set_field(tagId);
+ gaugeFieldMatcher->add_child()->set_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventUpgradeTimeNs);
+ event->write("some value");
+ event->write(2);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+ tagId, bucketStartTimeNs, pullerManager);
+
+ vector<shared_ptr<LogEvent>> allData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ event->write("some value");
+ event->write(1);
+ event->init();
+ allData.push_back(event);
+ gaugeProducer.onDataPulled(allData);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin()
+ ->second.front()
+ .mFields->begin()
+ ->second.value_int());
+
+ gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentBucketNum);
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin()
+ ->second.front()
+ .mFields->begin()
+ ->second.value_int());
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 1);
+ event->write("some value");
+ event->write(3);
+ event->init();
+ allData.push_back(event);
+ gaugeProducer.onDataPulled(allData);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin()
+ ->second.front()
+ .mFields->begin()
+ ->second.value_int());
+}
+
TEST(GaugeMetricProducerTest, TestWithCondition) {
GaugeMetric metric;
metric.set_id(metricId);
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 203f028..2658e4e 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -41,6 +41,11 @@
const int TagId = 1;
+const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
+const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
+const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
@@ -54,11 +59,13 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
uint64_t bucketStartTimeNs = 10000000000;
- uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
// Event starts again. This would not change anything as it already starts.
@@ -87,12 +94,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
@@ -101,15 +111,14 @@
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
EXPECT_TRUE(tracker.mInfos.empty());
-
- EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
- EXPECT_EQ(1u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
+ EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets);
- EXPECT_EQ(2u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
- EXPECT_EQ(40ULL, buckets[eventKey][1].mDuration);
+ EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
+ EXPECT_EQ(1u, buckets[eventKey].size());
+ EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
}
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
@@ -122,12 +131,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
// The event starts.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -137,14 +149,18 @@
ConditionKey());
// The event stops at early 4th bucket.
+ // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls
+ // flushIfneeded.
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20,
false /*stop all*/);
- EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
- EXPECT_EQ(3u, buckets[eventKey].size());
- EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[eventKey][0].mDuration);
- EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[eventKey][1].mDuration);
- EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[eventKey][2].mDuration);
+ EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
+
+ tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets);
+ EXPECT_EQ(1u, buckets[eventKey].size());
+ EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
}
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
@@ -157,12 +173,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, {});
// 2 starts
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -171,21 +190,16 @@
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets);
-
- EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
- EXPECT_EQ(2u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
- EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
+ // Because of nesting, still not stopped.
+ EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
// real stop now.
tracker.noteStop(DEFAULT_DIMENSION_KEY,
bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
- EXPECT_EQ(3u, buckets[eventKey].size());
- EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
- EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
- EXPECT_EQ(5ULL, buckets[eventKey][2].mDuration);
+ EXPECT_EQ(1u, buckets[eventKey].size());
+ EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration);
}
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
@@ -204,14 +218,17 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, true, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ true, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
@@ -226,45 +243,6 @@
EXPECT_EQ(5ULL, buckets[eventKey][0].mDuration);
}
-TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
- const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
- const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
- const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
- FieldMatcher dimensionInCondition;
- int64_t metricId = 1;
- Alert alert;
- alert.set_id(101);
- alert.set_metric_id(metricId);
- alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
- alert.set_num_buckets(2);
- const int32_t refPeriodSec = 1;
- alert.set_refractory_period_secs(refPeriodSec);
-
- unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
- uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
- uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
- uint64_t bucketSizeNs = 30 * NS_PER_SEC;
-
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
-
- tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey());
- tracker.noteStop(key1, eventStartTimeNs + 10, false);
- EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
- EXPECT_EQ(10LL, tracker.mDuration);
-
- tracker.noteStart(key2, true, eventStartTimeNs + 20, ConditionKey());
- tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
- tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
- EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
-
- EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
- (eventStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3 + refPeriodSec);
-}
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 80e16a1..4b579b1 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -38,6 +38,12 @@
const ConfigKey kConfigKey(0, 12345);
const int TagId = 1;
const int64_t metricId = 123;
+const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
+
+const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
+const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
+const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
TEST(OringDurationTrackerTest, TestDurationOverlap) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
@@ -51,13 +57,16 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, false, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -84,12 +93,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -115,12 +127,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -145,13 +160,16 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, false, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -190,13 +208,16 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
- uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, true, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -231,12 +252,15 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t durationTimeNs = 2 * 1000;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- false, bucketStartTimeNs, bucketSizeNs, true, {});
+ false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
@@ -271,11 +295,14 @@
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
uint64_t bucketStartTimeNs = 10000000000;
- uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, true, {});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -311,12 +338,14 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
- uint64_t bucketSizeNs = 30 * NS_PER_SEC;
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker});
+ true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, true, {anomalyTracker});
// Nothing in the past bucket.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -379,12 +408,14 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
+ uint64_t bucketNum = 0;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
- uint64_t bucketSizeNs = 30 * NS_PER_SEC;
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
+ true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ bucketSizeNs, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
@@ -434,8 +465,8 @@
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
- true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false,
- {anomalyTracker});
+ true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
+ bucketSizeNs, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 55c078d..325a372 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -44,6 +44,7 @@
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
+const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
/*
* Tests pulled atoms with no conditions
@@ -202,6 +203,101 @@
EXPECT_EQ(false, curInterval.startUpdated);
}
+TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) {
+ ValueMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs,
+ pullerManager);
+
+ shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event1->write(1);
+ event1->write(10);
+ event1->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+
+ valueProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+
+ shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC);
+ event2->write(1);
+ event2->write(10);
+ event2->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+
+ // Next value should create a new bucket.
+ shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC);
+ event3->write(1);
+ event3->write(10);
+ event3->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+ EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
+}
+
+TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
+ ValueMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(ONE_MINUTE);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager =
+ make_shared<StrictMock<MockStatsPullerManager>>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event->write(tagId);
+ event->write(120);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+ ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, tagId, bucketStartTimeNs,
+ pullerManager);
+
+ vector<shared_ptr<LogEvent>> allData;
+ allData.clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ event->write(tagId);
+ event->write(100);
+ event->init();
+ allData.push_back(event);
+
+ valueProducer.onDataPulled(allData);
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+
+ valueProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)eventUpgradeTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(20L, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mValue);
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+ event->write(tagId);
+ event->write(150);
+ event->init();
+ allData.push_back(event);
+ valueProducer.onDataPulled(allData);
+ EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ((uint64_t)bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(30L, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mValue);
+}
+
TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
ValueMetric metric;
metric.set_id(metricId);
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 0a97456..b48de54 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -15,6 +15,7 @@
#include "src/condition/ConditionWizard.h"
#include "src/external/StatsPullerManager.h"
+#include "src/packages/UidMap.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -40,6 +41,11 @@
MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
};
+class MockUidMap : public UidMap {
+ public:
+ MOCK_CONST_METHOD1(getHostUidOrSelf, int(int uid));
+};
+
HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value);
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
index cd76c9d..52673fb 100644
--- a/cmds/statsd/tools/dogfood/AndroidManifest.xml
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -37,5 +37,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <service android:name=".MainActivity$ReceiverIntentService" android:exported="true" />
</application>
</manifest>
diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
index 5d35c29..784ed40 100644
--- a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
+++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
@@ -31,6 +31,18 @@
android:layout_height="wrap_content"
android:background="@android:color/holo_green_light"
android:text="@string/push_config"/>
+ <Button
+ android:id="@+id/set_receiver"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_green_light"
+ android:text="@string/set_receiver"/>
+ <Button
+ android:id="@+id/remove_receiver"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_green_light"
+ android:text="@string/remove_receiver"/>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml
index 0eab0f4..60948a1 100644
--- a/cmds/statsd/tools/dogfood/res/values/strings.xml
+++ b/cmds/statsd/tools/dogfood/res/values/strings.xml
@@ -24,6 +24,8 @@
<string name="statsd_not_running">Statsd NOT Running</string>
<string name="push_config">Push baseline config</string>
+ <string name="set_receiver">Set pendingintent</string>
+ <string name="remove_receiver">Remove pendingintent</string>
<string name="app_a_foreground">App A foreground</string>
<string name="app_b_foreground">App B foreground</string>
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
index 33c8abf..03e5fef 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
@@ -32,11 +32,13 @@
for (StatsLog.ConfigMetricsReport report : reports.getReportsList()) {
sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
+ sb.append("Last report time:").append(getDateStr(report.getLastReportNanos())).
+ append("\n");
+ sb.append("Current report time:").append(getDateStr(report.getCurrentReportNanos())).
+ append("\n");
for (StatsLog.StatsLogReport log : report.getMetricsList()) {
sb.append("\n\n");
sb.append("metric id: ").append(log.getMetricId()).append("\n");
- sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
- sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
switch (log.getDataCase()) {
case DURATION_METRICS:
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 57575ae..0e6c933 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -16,7 +16,10 @@
package com.android.statsd.dogfood;
import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.IntentService;
import android.app.StatsManager;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
@@ -48,69 +51,69 @@
findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockAcquire(0, "wl_1");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(0, "wl_1");
+ }
+ });
findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockAcquire(1, "wl_1");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(1, "wl_1");
+ }
+ });
findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockAcquire(0, "wl_2");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(0, "wl_2");
+ }
+ });
findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockAcquire(1, "wl_2");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(1, "wl_2");
+ }
+ });
findViewById(R.id.app_a_wake_lock_release1).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockRelease(0, "wl_1");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(0, "wl_1");
+ }
+ });
findViewById(R.id.app_b_wake_lock_release1).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockRelease(1, "wl_1");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(1, "wl_1");
+ }
+ });
findViewById(R.id.app_a_wake_lock_release2).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockRelease(0, "wl_2");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(0, "wl_2");
+ }
+ });
findViewById(R.id.app_b_wake_lock_release2).setOnClickListener(
new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onWakeLockRelease(1, "wl_2");
- }
- });
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(1, "wl_2");
+ }
+ });
findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() {
@@ -191,8 +194,7 @@
byte[] config = new byte[inputStream.available()];
inputStream.read(config);
if (mStatsManager != null) {
- if (mStatsManager.addConfiguration(CONFIG_ID,
- config, getPackageName(), MainActivity.this.getClass().getName())) {
+ if (mStatsManager.addConfiguration(CONFIG_ID, config)) {
Toast.makeText(
MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
} else {
@@ -205,6 +207,55 @@
}
}
});
+
+ PendingIntent pi = PendingIntent.getService(this, 0,
+ new Intent(this, ReceiverIntentService.class), PendingIntent.FLAG_UPDATE_CURRENT);
+ findViewById(R.id.set_receiver).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ if (!statsdRunning()) {
+ return;
+ }
+ if (mStatsManager != null) {
+ if (mStatsManager.setDataFetchOperation(CONFIG_ID, pi)) {
+ Toast.makeText(MainActivity.this,
+ "Receiver specified to pending intent", Toast.LENGTH_LONG)
+ .show();
+ } else {
+ Toast.makeText(MainActivity.this, "Statsd did not set receiver",
+ Toast.LENGTH_LONG)
+ .show();
+ }
+ }
+ } catch (Exception e) {
+ Toast.makeText(MainActivity.this, "failed to set receiver", Toast.LENGTH_LONG);
+ }
+ }
+ });
+ findViewById(R.id.remove_receiver).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ if (!statsdRunning()) {
+ return;
+ }
+ if (mStatsManager != null) {
+ if (mStatsManager.setDataFetchOperation(CONFIG_ID, null)) {
+ Toast.makeText(MainActivity.this, "Receiver remove", Toast.LENGTH_LONG)
+ .show();
+ } else {
+ Toast.makeText(MainActivity.this, "Statsd did not remove receiver",
+ Toast.LENGTH_LONG)
+ .show();
+ }
+ }
+ } catch (Exception e) {
+ Toast.makeText(
+ MainActivity.this, "failed to remove receiver", Toast.LENGTH_LONG);
+ }
+ }
+ });
mStatsManager = (StatsManager) getSystemService("stats");
}
@@ -257,8 +308,8 @@
Log.d(TAG, "invalid pkg id");
return;
}
- int[] uids = new int[] {mUids[id]};
- String[] tags = new String[] {"acquire"};
+ int[] uids = new int[]{mUids[id]};
+ String[] tags = new String[]{"acquire"};
StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags,
StatsLog.WAKELOCK_STATE_CHANGED__LEVEL__PARTIAL_WAKE_LOCK, name,
StatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
@@ -273,8 +324,8 @@
Log.d(TAG, "invalid pkg id");
return;
}
- int[] uids = new int[] {mUids[id]};
- String[] tags = new String[] {"release"};
+ int[] uids = new int[]{mUids[id]};
+ String[] tags = new String[]{"release"};
StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags,
StatsLog.WAKELOCK_STATE_CHANGED__LEVEL__PARTIAL_WAKE_LOCK, name,
StatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
@@ -283,4 +334,20 @@
.append(", ").append(name).append(", 0);");
Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
}
+
+ public static class ReceiverIntentService extends IntentService {
+ public ReceiverIntentService() {
+ super("ReceiverIntentService");
+ }
+
+ /**
+ * The IntentService calls this method from the default worker thread with
+ * the intent that started the service. When this method returns, IntentService
+ * stops the service, as appropriate.
+ */
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.i(TAG, "Received notification that we should call getData");
+ }
+ }
}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
index 75489ad..87b82c2 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
@@ -36,6 +36,10 @@
int numMetrics = 0;
for (StatsLog.ConfigMetricsReport report : reports.getReportsList()) {
sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
+ sb.append("Last report time:").append(getDateStr(report.getLastReportNanos())).
+ append("\n");
+ sb.append("Current report time:").append(getDateStr(report.getCurrentReportNanos())).
+ append("\n");
for (StatsLog.StatsLogReport log : report.getMetricsList()) {
numMetrics++;
if (numMetrics > MAX_NUM_METRICS_TO_DISPLAY) {
@@ -45,8 +49,6 @@
}
sb.append("\n");
sb.append("metric id: ").append(log.getMetricId()).append("\n");
- sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
- sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
switch (log.getDataCase()) {
case DURATION_METRICS:
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index 652f6b2..bed4d98 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -47,10 +47,12 @@
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
+
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.os.StatsLog.StatsdStatsReport;
import com.android.internal.os.StatsdConfigProto.TimeUnit;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -60,18 +62,18 @@
* Runs a load test for statsd.
* How it works:
* <ul>
- * <li> Sets up and pushes a custom config with metrics that exercise a large swath of code paths.
- * <li> Periodically logs certain atoms into logd.
- * <li> Impact on battery can be printed to logcat, or a bug report can be filed and analyzed
- * in battery Historian.
+ * <li> Sets up and pushes a custom config with metrics that exercise a large swath of code paths.
+ * <li> Periodically logs certain atoms into logd.
+ * <li> Impact on battery can be printed to logcat, or a bug report can be filed and analyzed
+ * in battery Historian.
* </ul>
* The load depends on how demanding the config is, as well as how frequently atoms are pushsed
* to logd. Those are all controlled by 4 adjustable parameters:
* <ul>
- * <li> The 'replication' parameter artificially multiplies the number of metrics in the config.
- * <li> The bucket size controls the time-bucketing the aggregate metrics.
- * <li> The period parameter controls how frequently atoms are pushed to logd.
- * <li> The 'burst' parameter controls how many atoms are pushed at the same time (per period).
+ * <li> The 'replication' parameter artificially multiplies the number of metrics in the config.
+ * <li> The bucket size controls the time-bucketing the aggregate metrics.
+ * <li> The period parameter controls how frequently atoms are pushed to logd.
+ * <li> The 'burst' parameter controls how many atoms are pushed at the same time (per period).
* </ul>
*/
public class LoadtestActivity extends Activity implements AdapterView.OnItemSelectedListener {
@@ -93,7 +95,7 @@
Intent activityIntent = new Intent(context, LoadtestActivity.class);
activityIntent.putExtra(TYPE, PUSH_ALARM);
context.startActivity(activityIntent);
- }
+ }
}
public final static class StopperAlarmReceiver extends BroadcastReceiver {
@@ -102,7 +104,7 @@
Intent activityIntent = new Intent(context, LoadtestActivity.class);
activityIntent.putExtra(TYPE, STOP);
context.startActivity(activityIntent);
- }
+ }
}
private static Map<String, TimeUnit> initializeTimeUnitMap() {
@@ -119,6 +121,7 @@
labels.put("1s", TimeUnit.CTS);
return labels;
}
+
private static List<String> initializeTimeUnitLabels() {
List<String> labels = new ArrayList();
labels.add("1s");
@@ -136,10 +139,14 @@
private AlarmManager mAlarmMgr;
- /** Used to periodically log atoms to logd. */
+ /**
+ * Used to periodically log atoms to logd.
+ */
private PendingIntent mPushPendingIntent;
- /** Used to end the loadtest. */
+ /**
+ * Used to end the loadtest.
+ */
private PendingIntent mStopPendingIntent;
private Button mStartStop;
@@ -156,13 +163,19 @@
private CheckBox mValueMetricCheckBox;
private CheckBox mGaugeMetricCheckBox;
- /** When the load test started. */
+ /**
+ * When the load test started.
+ */
private long mStartedTimeMillis;
- /** For measuring perf data. */
+ /**
+ * For measuring perf data.
+ */
private PerfData mPerfData;
- /** For communicating with statsd. */
+ /**
+ * For communicating with statsd.
+ */
private StatsManager mStatsManager;
private PowerManager mPowerManager;
@@ -199,34 +212,54 @@
*/
private boolean mIncludeGaugeMetric;
- /** The burst size. */
+ /**
+ * The burst size.
+ */
private int mBurst;
- /** The metrics replication. */
+ /**
+ * The metrics replication.
+ */
private int mReplication;
- /** The period, in seconds, at which batches of atoms are pushed. */
+ /**
+ * The period, in seconds, at which batches of atoms are pushed.
+ */
private long mPeriodSecs;
- /** The bucket size, in minutes, for aggregate metrics. */
+ /**
+ * The bucket size, in minutes, for aggregate metrics.
+ */
private TimeUnit mBucket;
- /** The duration, in minutes, of the loadtest. */
+ /**
+ * The duration, in minutes, of the loadtest.
+ */
private long mDurationMins;
- /** Whether the loadtest has started. */
+ /**
+ * Whether the loadtest has started.
+ */
private boolean mStarted = false;
- /** Orchestrates the logging of pushed events into logd. */
+ /**
+ * Orchestrates the logging of pushed events into logd.
+ */
private SequencePusher mPusher;
- /** Generates statsd configs. */
+ /**
+ * Generates statsd configs.
+ */
private ConfigFactory mFactory;
- /** For intra-minute periods. */
+ /**
+ * For intra-minute periods.
+ */
private final Handler mHandler = new Handler();
- /** Number of metrics in the current config. */
+ /**
+ * Number of metrics in the current config.
+ */
private int mNumMetrics;
@Override
@@ -250,7 +283,7 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
InputMethodManager imm =
- (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (getCurrentFocus() != null) {
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}
@@ -380,10 +413,10 @@
}
// Piggy-back on that alarm to show the elapsed time.
long elapsedTimeMins = (long) Math.floor(
- (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
+ (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
mReportText.setText("Loadtest in progress.\n"
- + "num metrics =" + mNumMetrics
- + "\nElapsed time = " + elapsedTimeMins + " min(s)");
+ + "num metrics =" + mNumMetrics
+ + "\nElapsed time = " + elapsedTimeMins + " min(s)");
}
private void onAlarm() {
@@ -402,12 +435,14 @@
mWakeLock = null;
}
- /** Schedules the next cycle of pushing atoms into logd. */
+ /**
+ * Schedules the next cycle of pushing atoms into logd.
+ */
private void scheduleNext() {
Intent intent = new Intent(this, PusherAlarmReceiver.class);
intent.putExtra(TYPE, PUSH_ALARM);
mPushPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
- long nextTime = SystemClock.elapsedRealtime() + mPeriodSecs * 1000;
+ long nextTime = SystemClock.elapsedRealtime() + mPeriodSecs * 1000;
mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mPushPendingIntent);
}
@@ -433,7 +468,7 @@
Intent intent = new Intent(this, StopperAlarmReceiver.class);
intent.putExtra(TYPE, STOP);
mStopPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
- long nextTime = SystemClock.elapsedRealtime() + mDurationMins * 60 * 1000;
+ long nextTime = SystemClock.elapsedRealtime() + mDurationMins * 60 * 1000;
mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mStopPendingIntent);
// Log atoms.
@@ -441,8 +476,8 @@
// Start tracking performance.
mPerfData = new PerfData(this, mPlacebo, mReplication, mBucket, mPeriodSecs, mBurst,
- mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, mIncludeValueMetric,
- mIncludeGaugeMetric);
+ mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, mIncludeValueMetric,
+ mIncludeGaugeMetric);
mPerfData.startRecording(this);
mReportText.setText("Loadtest in progress.\nnum metrics =" + mNumMetrics);
@@ -476,7 +511,7 @@
getData();
long elapsedTimeMins = (long) Math.floor(
- (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
+ (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000);
mReportText.setText("Loadtest ended. Elapsed time = " + elapsedTimeMins + " min(s)");
clearConfigs();
updateStarted(false);
@@ -485,7 +520,7 @@
private synchronized void updateStarted(boolean started) {
mStarted = started;
mStartStop.setBackgroundColor(started ?
- Color.parseColor("#FFFF0000") : Color.parseColor("#FF00FF00"));
+ Color.parseColor("#FFFF0000") : Color.parseColor("#FF00FF00"));
mStartStop.setText(started ? getString(R.string.stop) : getString(R.string.start));
updateControlsEnabled();
}
@@ -538,22 +573,21 @@
}
private boolean setConfig(ConfigFactory.ConfigMetadata configData) {
- if (mStatsManager != null) {
- if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID,
- configData.bytes, getPackageName(), LoadtestActivity.this.getClass().getName())) {
+ if (mStatsManager != null) {
+ if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID, configData.bytes)) {
mNumMetrics = configData.numMetrics;
Log.d(TAG, "Config pushed to statsd");
return true;
} else {
Log.d(TAG, "Failed to push config to statsd");
}
- }
- return false;
+ }
+ return false;
}
private synchronized void setReplication(int replication) {
if (mStarted) {
- return;
+ return;
}
mReplicationText.setText("" + replication);
}
@@ -614,13 +648,13 @@
mBucketSpinner = (Spinner) findViewById(R.id.bucket_spinner);
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(
- this, R.layout.spinner_item, TIME_UNIT_LABELS);
+ this, R.layout.spinner_item, TIME_UNIT_LABELS);
mBucketSpinner.setAdapter(dataAdapter);
mBucketSpinner.setOnItemSelectedListener(this);
for (String label : TIME_UNIT_MAP.keySet()) {
- if (defaultValue.equals(TIME_UNIT_MAP.get(label).toString())) {
+ if (defaultValue.equals(TIME_UNIT_MAP.get(label).toString())) {
mBucketSpinner.setSelection(dataAdapter.getPosition(label));
}
}
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 58936fc..71de479 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -7,15 +7,18 @@
Landroid/app/Activity;->getActivityOptions()Landroid/app/ActivityOptions;
Landroid/app/Activity;->getActivityToken()Landroid/os/IBinder;
Landroid/app/Activity;->mActivityInfo:Landroid/content/pm/ActivityInfo;
+Landroid/app/ActivityManager;->addOnUidImportanceListener(Landroid/app/ActivityManager$OnUidImportanceListener;I)V
Landroid/app/ActivityManager;->clearApplicationUserData(Ljava/lang/String;Landroid/content/pm/IPackageDataObserver;)Z
Landroid/app/ActivityManager;->forceStopPackage(Ljava/lang/String;)V
Landroid/app/ActivityManager;->getCurrentUser()I
+Landroid/app/ActivityManager;->getPackageImportance(Ljava/lang/String;)I
Landroid/app/ActivityManager;->isLowRamDeviceStatic()Z
Landroid/app/ActivityManager;->isUserRunning(I)Z
Landroid/app/ActivityManager;->mContext:Landroid/content/Context;
Landroid/app/ActivityManagerNative;->getDefault()Landroid/app/IActivityManager;
Landroid/app/ActivityManager;->PROCESS_STATE_TOP:I
Landroid/app/ActivityManager$RecentTaskInfo;->firstActiveTime:J
+Landroid/app/ActivityManager;->removeOnUidImportanceListener(Landroid/app/ActivityManager$OnUidImportanceListener;)V
Landroid/app/ActivityManager$RunningAppProcessInfo;->flags:I
Landroid/app/ActivityManager$RunningAppProcessInfo;->processState:I
Landroid/app/Activity;->mApplication:Landroid/app/Application;
@@ -43,7 +46,6 @@
Landroid/app/ActivityThread$H;->CREATE_SERVICE:I
Landroid/app/ActivityThread$H;->EXIT_APPLICATION:I
Landroid/app/ActivityThread$H;->RECEIVER:I
-Landroid/app/ActivityThread$H;->RELAUNCH_ACTIVITY:I
Landroid/app/ActivityThread$H;->REMOVE_PROVIDER:I
Landroid/app/ActivityThread$H;->SERVICE_ARGS:I
Landroid/app/ActivityThread$H;->STOP_SERVICE:I
@@ -58,6 +60,9 @@
Landroid/app/ActivityThread;->mPackages:Landroid/util/ArrayMap;
Landroid/app/ActivityThread;->performStopActivity(Landroid/os/IBinder;ZLjava/lang/String;)V
Landroid/app/ActivityThread;->sPackageManager:Landroid/content/pm/IPackageManager;
+Landroid/app/admin/DevicePolicyManager;->getDeviceOwnerComponentOnAnyUser()Landroid/content/ComponentName;
+Landroid/app/admin/DevicePolicyManager;->getDeviceOwner()Ljava/lang/String;
+Landroid/app/admin/DevicePolicyManager;->getProfileOwner()Landroid/content/ComponentName;
Landroid/app/admin/DevicePolicyManager;->getTrustAgentConfiguration(Landroid/content/ComponentName;Landroid/content/ComponentName;I)Ljava/util/List;
Landroid/app/admin/DevicePolicyManager;->notifyPendingSystemUpdate(J)V
Landroid/app/admin/DevicePolicyManager;->notifyPendingSystemUpdate(JZ)V
@@ -65,6 +70,14 @@
Landroid/app/admin/DevicePolicyManager;->packageHasActiveAdmins(Ljava/lang/String;)Z
Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;Z)V
+Landroid/app/AlarmManager;->FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED:I
+Landroid/app/AlarmManager;->FLAG_IDLE_UNTIL:I
+Landroid/app/AlarmManager;->FLAG_STANDALONE:I
+Landroid/app/AlarmManager;->FLAG_WAKE_FROM_IDLE:I
+Landroid/app/AlarmManager;->set(IJJJLandroid/app/AlarmManager$OnAlarmListener;Landroid/os/Handler;Landroid/os/WorkSource;)V
+Landroid/app/AlarmManager;->set(IJJJLandroid/app/PendingIntent;Landroid/os/WorkSource;)V
+Landroid/app/AlarmManager;->WINDOW_EXACT:J
+Landroid/app/AlarmManager;->WINDOW_HEURISTIC:J
Landroid/app/AlertDialog$Builder;->P:Lcom/android/internal/app/AlertController$AlertParams;
Landroid/app/AlertDialog;->mAlert:Lcom/android/internal/app/AlertController;
Landroid/app/AppGlobals;->getInitialApplication()Landroid/app/Application;
@@ -86,9 +99,32 @@
Landroid/app/AppOpsManager;->OP_WRITE_SMS:I
Landroid/app/AppOpsManager;->setMode(IILjava/lang/String;I)V
Landroid/app/AppOpsManager;->strOpToOp(Ljava/lang/String;)I
+Landroid/app/backup/BackupDataInput;-><init>(Ljava/io/FileDescriptor;)V
Landroid/app/backup/BackupDataInputStream;->dataSize:I
Landroid/app/backup/BackupDataInputStream;->key:Ljava/lang/String;
+Landroid/app/backup/BackupDataOutput;-><init>(Ljava/io/FileDescriptor;)V
+Landroid/app/backup/BackupManager;->backupNow()V
+Landroid/app/backup/BackupManager;->beginRestoreSession()Landroid/app/backup/RestoreSession;
+Landroid/app/backup/BackupManager;->cancelBackups()V
+Landroid/app/backup/BackupManager;->getAvailableRestoreToken(Ljava/lang/String;)J
+Landroid/app/backup/BackupManager;->getCurrentTransport()Ljava/lang/String;
+Landroid/app/backup/BackupManager;->isBackupEnabled()Z
+Landroid/app/backup/BackupManager;->listAllTransports()[Ljava/lang/String;
+Landroid/app/backup/BackupManagerMonitor;-><init>()V
+Landroid/app/backup/BackupManager;->requestBackup([Ljava/lang/String;Landroid/app/backup/BackupObserver;Landroid/app/backup/BackupManagerMonitor;I)I
+Landroid/app/backup/BackupManager;->selectBackupTransport(Landroid/content/ComponentName;Landroid/app/backup/SelectBackupTransportCallback;)V
+Landroid/app/backup/BackupManager;->selectBackupTransport(Ljava/lang/String;)Ljava/lang/String;
+Landroid/app/backup/BackupManager;->setAutoRestore(Z)V
+Landroid/app/backup/BackupManager;->setBackupEnabled(Z)V
+Landroid/app/backup/BackupObserver;-><init>()V
+Landroid/app/backup/BackupTransport;-><init>()V
Landroid/app/backup/FullBackup;->backupToTar(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/backup/FullBackupDataOutput;)I
+Landroid/app/backup/RestoreDescription;-><init>(Ljava/lang/String;I)V
+Landroid/app/backup/RestoreSession;->endRestoreSession()V
+Landroid/app/backup/RestoreSession;->getAvailableRestoreSets(Landroid/app/backup/RestoreObserver;)I
+Landroid/app/backup/RestoreSession;->restoreAll(JLandroid/app/backup/RestoreObserver;)I
+Landroid/app/backup/RestoreSet;-><init>(Ljava/lang/String;Ljava/lang/String;J)V
+Landroid/app/backup/SelectBackupTransportCallback;-><init>()V
Landroid/app/ContextImpl;->createActivityContext(Landroid/app/ActivityThread;Landroid/app/LoadedApk;Landroid/content/pm/ActivityInfo;Landroid/os/IBinder;ILandroid/content/res/Configuration;)Landroid/app/ContextImpl;
Landroid/app/ContextImpl;->getActivityToken()Landroid/os/IBinder;
Landroid/app/ContextImpl;->mMainThread:Landroid/app/ActivityThread;
@@ -107,6 +143,8 @@
Landroid/app/IActivityManager;->getLaunchedFromPackage(Landroid/os/IBinder;)Ljava/lang/String;
Landroid/app/IActivityManager;->resumeAppSwitches()V
Landroid/app/IApplicationThread;->scheduleTrimMemory(I)V
+Landroid/app/InstantAppResolverService;-><init>()V
+Landroid/app/InstantAppResolverService$InstantAppResolutionCallback;->onInstantAppResolveInfo(Ljava/util/List;)V
Landroid/app/IntentService;->mServiceHandler:Landroid/app/IntentService$ServiceHandler;
Landroid/app/IWallpaperManager;->getWallpaper(Ljava/lang/String;Landroid/app/IWallpaperManagerCallback;ILandroid/os/Bundle;I)Landroid/os/ParcelFileDescriptor;
Landroid/app/LoadedApk;->mApplication:Landroid/app/Application;
@@ -133,6 +171,10 @@
Landroid/app/StatusBarManager;->expandSettingsPanel(Ljava/lang/String;)V
Landroid/app/StatusBarManager;->expandSettingsPanel()V
Landroid/app/TimePickerDialog;->mTimePicker:Landroid/widget/TimePicker;
+Landroid/app/usage/UsageStatsManager;->getAppStandbyBuckets()Ljava/util/Map;
+Landroid/app/usage/UsageStatsManager;->setAppStandbyBuckets(Ljava/util/Map;)V
+Landroid/app/usage/UsageStatsManager;->whitelistAppTemporarily(Ljava/lang/String;JLandroid/os/UserHandle;)V
+Landroid/app/WallpaperColors;->getColorHints()I
Landroid/app/WallpaperManager;->getBitmap()Landroid/graphics/Bitmap;
Landroid/app/WallpaperManager;->getBitmap(Z)Landroid/graphics/Bitmap;
Landroid/app/WallpaperManager;->getIWallpaperManager()Landroid/app/IWallpaperManager;
@@ -143,9 +185,13 @@
Landroid/appwidget/AppWidgetManager;->bindAppWidgetId(ILandroid/content/ComponentName;Landroid/os/Bundle;)V
Landroid/appwidget/AppWidgetManager;->bindAppWidgetId(ILandroid/content/ComponentName;)V
Landroid/bluetooth/BluetoothA2dp;->connect(Landroid/bluetooth/BluetoothDevice;)Z
+Landroid/bluetooth/BluetoothAdapter;->disableBLE()Z
Landroid/bluetooth/BluetoothAdapter;->disable(Z)Z
+Landroid/bluetooth/BluetoothAdapter;->enableBLE()Z
Landroid/bluetooth/BluetoothAdapter;->enableNoAutoConnect()Z
Landroid/bluetooth/BluetoothAdapter;->getDiscoverableTimeout()I
+Landroid/bluetooth/BluetoothAdapter;->isBleScanAlwaysAvailable()Z
+Landroid/bluetooth/BluetoothAdapter;->isLeEnabled()Z
Landroid/bluetooth/BluetoothAdapter;->setScanMode(II)Z
Landroid/bluetooth/BluetoothAdapter;->setScanMode(I)Z
Landroid/bluetooth/BluetoothDevice;->cancelBondProcess()Z
@@ -164,6 +210,8 @@
Landroid/bluetooth/BluetoothHeadset;->setPriority(Landroid/bluetooth/BluetoothDevice;I)Z
Landroid/bluetooth/BluetoothUuid;->RESERVED_UUIDS:[Landroid/os/ParcelUuid;
Landroid/bluetooth/IBluetooth$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetooth;
+Landroid/bluetooth/le/BluetoothLeScanner;->startScanFromSource(Ljava/util/List;Landroid/bluetooth/le/ScanSettings;Landroid/os/WorkSource;Landroid/bluetooth/le/ScanCallback;)V
+Landroid/bluetooth/le/ScanSettings$Builder;->setScanResultType(I)Landroid/bluetooth/le/ScanSettings$Builder;
Landroid/content/AsyncTaskLoader;->mExecutor:Ljava/util/concurrent/Executor;
Landroid/content/ContentProviderOperation;->mSelection:Ljava/lang/String;
Landroid/content/ContentProviderOperation;->mType:I
@@ -171,13 +219,17 @@
Landroid/content/ContentResolver;->getSyncStatus(Landroid/accounts/Account;Ljava/lang/String;)Landroid/content/SyncStatusInfo;
Landroid/content/ContentResolver;->mContext:Landroid/content/Context;
Landroid/content/ContentValues;->mValues:Ljava/util/HashMap;
+Landroid/content/Context;->bindServiceAsUser(Landroid/content/Intent;Landroid/content/ServiceConnection;ILandroid/os/UserHandle;)Z
+Landroid/content/Context;->createCredentialProtectedStorageContext()Landroid/content/Context;
Landroid/content/Context;->createPackageContextAsUser(Ljava/lang/String;ILandroid/os/UserHandle;)Landroid/content/Context;
Landroid/content/Context;->getSharedPrefsFile(Ljava/lang/String;)Ljava/io/File;
Landroid/content/Context;->getThemeResId()I
+Landroid/content/Context;->isCredentialProtectedStorage()Z
Landroid/content/Context;->sendBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;I)V
Landroid/content/Context;->sendBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;Landroid/os/Bundle;)V
Landroid/content/Context;->sendOrderedBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;ILandroid/content/BroadcastReceiver;Landroid/os/Handler;ILjava/lang/String;Landroid/os/Bundle;)V
Landroid/content/Context;->sendOrderedBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;ILandroid/os/Bundle;Landroid/content/BroadcastReceiver;Landroid/os/Handler;ILjava/lang/String;Landroid/os/Bundle;)V
+Landroid/content/ContextWrapper;->createCredentialProtectedStorageContext()Landroid/content/Context;
Landroid/content/ContextWrapper;->mBase:Landroid/content/Context;
Landroid/content/CursorLoader;->mCancellationSignal:Landroid/os/CancellationSignal;
Landroid/content/CursorLoader;->mObserver:Landroid/content/Loader$ForceLoadContentObserver;
@@ -185,6 +237,7 @@
Landroid/content/IContentService;->getMasterSyncAutomatically()Z
Landroid/content/IContentService;->setMasterSyncAutomatically(Z)V
Landroid/content/Intent;->ACTION_ALARM_CHANGED:Ljava/lang/String;
+Landroid/content/IntentFilter;->setOrder(I)V
Landroid/content/Intent;->putExtra(Ljava/lang/String;Landroid/os/IBinder;)Landroid/content/Intent;
Landroid/content/pm/ApplicationInfo;->installLocation:I
Landroid/content/pm/ApplicationInfo;->isForwardLocked()Z
@@ -193,12 +246,19 @@
Landroid/content/pm/ApplicationInfo;->primaryCpuAbi:Ljava/lang/String;
Landroid/content/pm/ApplicationInfo;->privateFlags:I
Landroid/content/pm/ApplicationInfo;->targetSandboxVersion:I
+Landroid/content/pm/InstantAppIntentFilter;-><init>(Ljava/lang/String;Ljava/util/List;)V
+Landroid/content/pm/InstantAppResolveInfo;->getPackageName()Ljava/lang/String;
+Landroid/content/pm/InstantAppResolveInfo;-><init>(Landroid/content/pm/InstantAppResolveInfo$InstantAppDigest;Ljava/lang/String;Ljava/util/List;I)V
+Landroid/content/pm/InstantAppResolveInfo$InstantAppDigest;->getDigestPrefix()[I
+Landroid/content/pm/InstantAppResolveInfo$InstantAppDigest;-><init>(Ljava/lang/String;)V
Landroid/content/pm/IPackageManager;->getLastChosenActivity(Landroid/content/Intent;Ljava/lang/String;I)Landroid/content/pm/ResolveInfo;
Landroid/content/pm/IPackageManager;->setLastChosenActivity(Landroid/content/Intent;Ljava/lang/String;ILandroid/content/IntentFilter;ILandroid/content/ComponentName;)V
+Landroid/content/pm/IPackageStatsObserver$Stub;-><init>()V
Landroid/content/pm/LauncherApps;->mPm:Landroid/content/pm/PackageManager;
Landroid/content/pm/LauncherApps;->startShortcut(Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Rect;Landroid/os/Bundle;I)V
Landroid/content/pm/PackageInstaller$SessionParams;->setGrantedRuntimePermissions([Ljava/lang/String;)V
Landroid/content/pm/PackageInstaller$SessionParams;->setInstallAsInstantApp(Z)V
+Landroid/content/pm/PackageManager;->addOnPermissionsChangeListener(Landroid/content/pm/PackageManager$OnPermissionsChangedListener;)V
Landroid/content/pm/PackageManager;->freeStorageAndNotify(JLandroid/content/pm/IPackageDataObserver;)V
Landroid/content/pm/PackageManager;->freeStorageAndNotify(Ljava/lang/String;JLandroid/content/pm/IPackageDataObserver;)V
Landroid/content/pm/PackageManager;->freeStorage(JLandroid/content/IntentSender;)V
@@ -210,6 +270,8 @@
Landroid/content/pm/PackageManager;->getResourcesForApplicationAsUser(Ljava/lang/String;I)Landroid/content/res/Resources;
Landroid/content/pm/PackageManager;->movePackage(Ljava/lang/String;Landroid/os/storage/VolumeInfo;)I
Landroid/content/pm/PackageManager;->queryBroadcastReceivers(Landroid/content/Intent;II)Ljava/util/List;
+Landroid/content/pm/PackageManager;->removeOnPermissionsChangeListener(Landroid/content/pm/PackageManager$OnPermissionsChangedListener;)V
+Landroid/content/pm/PackageManager;->verifyIntentFilter(IILjava/util/List;)V
Landroid/content/pm/PackageParser;->collectCertificates(Landroid/content/pm/PackageParser$Package;Ljava/io/File;Z)V
Landroid/content/pm/PackageParser;->collectCertificates(Landroid/content/pm/PackageParser$Package;Z)V
Landroid/content/pm/PackageParser;->generatePackageInfo(Landroid/content/pm/PackageParser$Package;[IIJJLjava/util/Set;Landroid/content/pm/PackageUserState;I)Landroid/content/pm/PackageInfo;
@@ -220,28 +282,13 @@
Landroid/content/pm/UserInfo;->id:I
Landroid/content/pm/UserInfo;->isPrimary()Z
Landroid/content/res/AssetManager;->addAssetPath(Ljava/lang/String;)I
-Landroid/content/res/AssetManager;->addAssetPaths([Ljava/lang/String;)[I
-Landroid/content/res/AssetManager;->applyStyle(JIIJ[IIJJ)V
-Landroid/content/res/AssetManager;->getArraySize(I)I
Landroid/content/res/AssetManager;->getAssignedPackageIdentifiers()Landroid/util/SparseArray;
-Landroid/content/res/AssetManager;->getCookieName(I)Ljava/lang/String;
Landroid/content/res/AssetManager;->getResourceBagText(II)Ljava/lang/CharSequence;
-Landroid/content/res/AssetManager;->loadResourceBagValue(IILandroid/util/TypedValue;Z)I
-Landroid/content/res/AssetManager;->loadResourceValue(ISLandroid/util/TypedValue;Z)I
-Landroid/content/res/AssetManager;->loadThemeAttributeValue(JILandroid/util/TypedValue;Z)I
Landroid/content/res/AssetManager;->mObject:J
-Landroid/content/res/AssetManager;->openNonAssetFdNative(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;
Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;I)Ljava/io/InputStream;
Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;)Ljava/io/InputStream;
Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;I)Ljava/io/InputStream;
Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;)Ljava/io/InputStream;
-Landroid/content/res/AssetManager;->openNonAssetNative(ILjava/lang/String;I)J
-Landroid/content/res/AssetManager;->openXmlAssetNative(ILjava/lang/String;)J
-Landroid/content/res/AssetManager;->resolveAttrs(JII[I[I[I[I)Z
-Landroid/content/res/AssetManager;->retrieveArray(I[I)I
-Landroid/content/res/AssetManager;->retrieveAttributes(J[I[I[I)Z
-Landroid/content/res/AssetManager;->STYLE_NUM_ENTRIES:I
-Landroid/content/res/AssetManager;->STYLE_RESOURCE_ID:I
Landroid/content/res/DrawableCache;->getInstance(JLandroid/content/res/Resources;Landroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable;
Landroid/content/res/Resources;->getCompatibilityInfo()Landroid/content/res/CompatibilityInfo;
Landroid/content/res/ResourcesImpl;->mAccessLock:Ljava/lang/Object;
@@ -271,7 +318,6 @@
Landroid/content/res/TypedArray;->mTheme:Landroid/content/res/Resources$Theme;
Landroid/content/res/TypedArray;->mValue:Landroid/util/TypedValue;
Landroid/content/res/TypedArray;->mXml:Landroid/content/res/XmlBlock$Parser;
-Landroid/content/res/XmlBlock;->close()V
Landroid/content/res/XmlBlock;->newParser()Landroid/content/res/XmlResourceParser;
Landroid/content/res/XmlBlock$Parser;->mParseState:J
Landroid/content/SyncStatusInfo;->lastSuccessTime:J
@@ -301,6 +347,11 @@
Landroid/graphics/drawable/StateListDrawable;->getStateSet(I)[I
Landroid/graphics/drawable/StateListDrawable;->mStateListState:Landroid/graphics/drawable/StateListDrawable$StateListState;
Landroid/graphics/drawable/StateListDrawable;->updateStateFromTypedArray(Landroid/content/res/TypedArray;)V
+Landroid/graphics/FontFamily;->abortCreation()V
+Landroid/graphics/FontFamily;->addFontFromAssetManager(Landroid/content/res/AssetManager;Ljava/lang/String;IZIII[Landroid/graphics/fonts/FontVariationAxis;)Z
+Landroid/graphics/FontFamily;->addFontFromBuffer(Ljava/nio/ByteBuffer;I[Landroid/graphics/fonts/FontVariationAxis;II)Z
+Landroid/graphics/FontFamily;->freeze()Z
+Landroid/graphics/FontFamily;-><init>()V
Landroid/graphics/LinearGradient;->mColors:[I
Landroid/graphics/NinePatch;->mBitmap:Landroid/graphics/Bitmap;
Landroid/graphics/SurfaceTexture;->nativeDetachFromGLContext()I
@@ -310,18 +361,64 @@
Landroid/graphics/Typeface;->sSystemFontMap:Ljava/util/Map;
Landroid/hardware/Camera;->addCallbackBuffer([BI)V
Landroid/hardware/Camera;->openLegacy(II)Landroid/hardware/Camera;
+Landroid/hardware/display/DisplayManager;->getStableDisplaySize()Landroid/graphics/Point;
Landroid/hardware/display/WifiDisplayStatus;->mActiveDisplay:Landroid/hardware/display/WifiDisplay;
Landroid/hardware/display/WifiDisplayStatus;->mDisplays:[Landroid/hardware/display/WifiDisplay;
Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager;
Landroid/hardware/input/InputManager;->getInstance()Landroid/hardware/input/InputManager;
Landroid/hardware/input/InputManager;->mIm:Landroid/hardware/input/IInputManager;
+Landroid/hardware/location/ContextHubInfo;->getId()I
+Landroid/hardware/location/ContextHubInfo;->getMaxPacketLengthBytes()I
+Landroid/hardware/location/ContextHubManager$Callback;-><init>()V
+Landroid/hardware/location/ContextHubManager;->findNanoAppOnHub(ILandroid/hardware/location/NanoAppFilter;)[I
+Landroid/hardware/location/ContextHubManager;->getContextHubHandles()[I
+Landroid/hardware/location/ContextHubManager;->getContextHubInfo(I)Landroid/hardware/location/ContextHubInfo;
+Landroid/hardware/location/ContextHubManager;->getNanoAppInstanceInfo(I)Landroid/hardware/location/NanoAppInstanceInfo;
+Landroid/hardware/location/ContextHubManager;->loadNanoApp(ILandroid/hardware/location/NanoApp;)I
+Landroid/hardware/location/ContextHubManager;->registerCallback(Landroid/hardware/location/ContextHubManager$Callback;)I
+Landroid/hardware/location/ContextHubManager;->sendMessage(IILandroid/hardware/location/ContextHubMessage;)I
+Landroid/hardware/location/ContextHubManager;->unloadNanoApp(I)I
+Landroid/hardware/location/ContextHubMessage;->getData()[B
+Landroid/hardware/location/ContextHubMessage;->getMsgType()I
+Landroid/hardware/location/ContextHubMessage;->getVersion()I
+Landroid/hardware/location/ContextHubMessage;-><init>(II[B)V
+Landroid/hardware/location/GeofenceHardware;->addGeofence(IILandroid/hardware/location/GeofenceHardwareRequest;Landroid/hardware/location/GeofenceHardwareCallback;)Z
+Landroid/hardware/location/GeofenceHardwareCallback;-><init>()V
+Landroid/hardware/location/GeofenceHardwareCallback;->onGeofenceAdd(II)V
+Landroid/hardware/location/GeofenceHardwareCallback;->onGeofenceRemove(II)V
+Landroid/hardware/location/GeofenceHardwareCallback;->onGeofenceTransition(IILandroid/location/Location;JI)V
+Landroid/hardware/location/GeofenceHardware;->getMonitoringTypes()[I
+Landroid/hardware/location/GeofenceHardware;->getStatusOfMonitoringType(I)I
+Landroid/hardware/location/GeofenceHardwareMonitorCallback;-><init>()V
+Landroid/hardware/location/GeofenceHardwareMonitorCallback;->onMonitoringSystemChange(IZLandroid/location/Location;)V
+Landroid/hardware/location/GeofenceHardware;->registerForMonitorStateChangeCallback(ILandroid/hardware/location/GeofenceHardwareMonitorCallback;)Z
+Landroid/hardware/location/GeofenceHardware;->removeGeofence(II)Z
+Landroid/hardware/location/GeofenceHardwareRequest;->createCircularGeofence(DDD)Landroid/hardware/location/GeofenceHardwareRequest;
+Landroid/hardware/location/GeofenceHardwareRequest;->setLastTransition(I)V
+Landroid/hardware/location/GeofenceHardwareRequest;->setMonitorTransitions(I)V
+Landroid/hardware/location/GeofenceHardwareRequest;->setNotificationResponsiveness(I)V
+Landroid/hardware/location/GeofenceHardwareRequest;->setUnknownTimer(I)V
+Landroid/hardware/location/NanoAppFilter;-><init>(JIIJ)V
+Landroid/hardware/location/NanoApp;-><init>(J[B)V
+Landroid/hardware/location/NanoAppInstanceInfo;->getAppId()J
+Landroid/hardware/location/NanoAppInstanceInfo;->getAppVersion()I
+Landroid/hardware/location/NanoAppInstanceInfo;->getHandle()I
+Landroid/hardware/location/NanoAppInstanceInfo;->getName()Ljava/lang/String;
Landroid/hardware/usb/UsbManager;->setCurrentFunction(Ljava/lang/String;Z)V
+Landroid/location/GeocoderParams;->getClientPackage()Ljava/lang/String;
+Landroid/location/GeocoderParams;->getLocale()Ljava/util/Locale;
+Landroid/location/LocationManager;->requestLocationUpdates(Landroid/location/LocationRequest;Landroid/location/LocationListener;Landroid/os/Looper;)V
Landroid/location/LocationRequest;->createFromDeprecatedProvider(Ljava/lang/String;JFZ)Landroid/location/LocationRequest;
+Landroid/location/LocationRequest;->setHideFromAppOps(Z)V
Landroid/location/LocationRequest;->setWorkSource(Landroid/os/WorkSource;)V
Landroid/location/Location;->setIsFromMockProvider(Z)V
Landroid/media/AudioAttributes$Builder;->setInternalCapturePreset(I)Landroid/media/AudioAttributes$Builder;
+Landroid/media/AudioFocusInfo;->getClientId()Ljava/lang/String;
+Landroid/media/AudioFocusInfo;->getClientUid()I
+Landroid/media/AudioFocusInfo;->getLossReceived()I
Landroid/media/AudioManager;->abandonAudioFocus(Landroid/media/AudioManager$OnAudioFocusChangeListener;Landroid/media/AudioAttributes;)I
Landroid/media/AudioManager;->mAudioFocusIdListenerMap:Ljava/util/concurrent/ConcurrentHashMap;
+Landroid/media/AudioManager;->registerAudioPolicy(Landroid/media/audiopolicy/AudioPolicy;)I
Landroid/media/AudioManager;->requestAudioFocus(Landroid/media/AudioFocusRequest;Landroid/media/audiopolicy/AudioPolicy;)I
Landroid/media/AudioManager;->requestAudioFocus(Landroid/media/AudioManager$OnAudioFocusChangeListener;Landroid/media/AudioAttributes;II)I
Landroid/media/AudioManager;->requestAudioFocus(Landroid/media/AudioManager$OnAudioFocusChangeListener;Landroid/media/AudioAttributes;IILandroid/media/audiopolicy/AudioPolicy;)I
@@ -329,6 +426,21 @@
Landroid/media/AudioManager;->STREAM_BLUETOOTH_SCO:I
Landroid/media/AudioManager;->STREAM_SYSTEM_ENFORCED:I
Landroid/media/AudioManager;->STREAM_TTS:I
+Landroid/media/AudioManager;->unregisterAudioPolicyAsync(Landroid/media/audiopolicy/AudioPolicy;)V
+Landroid/media/audiopolicy/AudioMix$Builder;->build()Landroid/media/audiopolicy/AudioMix;
+Landroid/media/audiopolicy/AudioMix$Builder;-><init>(Landroid/media/audiopolicy/AudioMixingRule;)V
+Landroid/media/audiopolicy/AudioMix$Builder;->setFormat(Landroid/media/AudioFormat;)Landroid/media/audiopolicy/AudioMix$Builder;
+Landroid/media/audiopolicy/AudioMix$Builder;->setRouteFlags(I)Landroid/media/audiopolicy/AudioMix$Builder;
+Landroid/media/audiopolicy/AudioMixingRule$Builder;->addRule(Landroid/media/AudioAttributes;I)Landroid/media/audiopolicy/AudioMixingRule$Builder;
+Landroid/media/audiopolicy/AudioMixingRule$Builder;->build()Landroid/media/audiopolicy/AudioMixingRule;
+Landroid/media/audiopolicy/AudioMixingRule$Builder;-><init>()V
+Landroid/media/audiopolicy/AudioPolicy$AudioPolicyFocusListener;-><init>()V
+Landroid/media/audiopolicy/AudioPolicy$Builder;->addMix(Landroid/media/audiopolicy/AudioMix;)Landroid/media/audiopolicy/AudioPolicy$Builder;
+Landroid/media/audiopolicy/AudioPolicy$Builder;->build()Landroid/media/audiopolicy/AudioPolicy;
+Landroid/media/audiopolicy/AudioPolicy$Builder;-><init>(Landroid/content/Context;)V
+Landroid/media/audiopolicy/AudioPolicy$Builder;->setAudioPolicyFocusListener(Landroid/media/audiopolicy/AudioPolicy$AudioPolicyFocusListener;)V
+Landroid/media/audiopolicy/AudioPolicy$Builder;->setLooper(Landroid/os/Looper;)Landroid/media/audiopolicy/AudioPolicy$Builder;
+Landroid/media/audiopolicy/AudioPolicy;->createAudioRecordSink(Landroid/media/audiopolicy/AudioMix;)Landroid/media/AudioRecord;
Landroid/media/AudioSystem;->setDeviceConnectionState(IILjava/lang/String;Ljava/lang/String;)I
Landroid/media/AudioTrack;->getLatency()I
Landroid/media/IAudioService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IAudioService;
@@ -358,6 +470,19 @@
Landroid/media/Ringtone;->setVolume(F)V
Landroid/media/SubtitleController;->mHandler:Landroid/os/Handler;
Landroid/media/ThumbnailUtils;->createImageThumbnail(Ljava/lang/String;I)Landroid/graphics/Bitmap;
+Landroid/metrics/LogMaker;->getCategory()I
+Landroid/metrics/LogMaker;->getCounterBucket()J
+Landroid/metrics/LogMaker;->getCounterName()Ljava/lang/String;
+Landroid/metrics/LogMaker;->getCounterValue()I
+Landroid/metrics/LogMaker;->getTimestamp()J
+Landroid/metrics/LogMaker;->isLongCounterBucket()Z
+Landroid/metrics/LogMaker;->serialize()[Ljava/lang/Object;
+Landroid/metrics/MetricsReader;->checkpoint()V
+Landroid/metrics/MetricsReader;->hasNext()Z
+Landroid/metrics/MetricsReader;-><init>()V
+Landroid/metrics/MetricsReader;->next()Landroid/metrics/LogMaker;
+Landroid/metrics/MetricsReader;->read(J)V
+Landroid/metrics/MetricsReader;->reset()V
Landroid/net/ConnectivityManager;->ACTION_TETHER_STATE_CHANGED:Ljava/lang/String;
Landroid/net/ConnectivityManager;->EXTRA_ACTIVE_TETHER:Ljava/lang/String;
Landroid/net/ConnectivityManager;->getActiveLinkProperties()Landroid/net/LinkProperties;
@@ -370,11 +495,20 @@
Landroid/net/ConnectivityManager;->isNetworkTypeMobile(I)Z
Landroid/net/ConnectivityManager;->isTetheringSupported()Z
Landroid/net/ConnectivityManager;->mService:Landroid/net/IConnectivityManager;
+Landroid/net/ConnectivityManager$OnStartTetheringCallback;-><init>()V
Landroid/net/ConnectivityManager;->requestRouteToHostAddress(ILjava/net/InetAddress;)Z
Landroid/net/ConnectivityManager;->requestRouteToHost(II)Z
+Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V
+Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;)V
+Landroid/net/ConnectivityManager;->stopTethering(I)V
Landroid/net/INetworkStatsService$Stub$Proxy;->getMobileIfaces()[Ljava/lang/String;
Landroid/net/LinkProperties;->setHttpProxy(Landroid/net/ProxyInfo;)V
+Landroid/net/NetworkKey;-><init>(Landroid/net/WifiKey;)V
Landroid/net/NetworkPolicyManager;->mService:Landroid/net/INetworkPolicyManager;
+Landroid/net/NetworkRecommendationProvider;-><init>(Landroid/content/Context;Ljava/util/concurrent/Executor;)V
+Landroid/net/NetworkScoreManager;->clearScores()Z
+Landroid/net/NetworkScoreManager;->getActiveScorerPackage()Ljava/lang/String;
+Landroid/net/NetworkScoreManager;->updateScores([Landroid/net/ScoredNetwork;)Z
Landroid/net/NetworkStats;->capacity:I
Landroid/net/NetworkStats;->defaultNetwork:[I
Landroid/net/NetworkStats;->iface:[Ljava/lang/String;
@@ -389,37 +523,90 @@
Landroid/net/NetworkStats;->txBytes:[J
Landroid/net/NetworkStats;->txPackets:[J
Landroid/net/NetworkStats;->uid:[I
+Landroid/net/RssiCurve;-><init>(II[BI)V
+Landroid/net/RssiCurve;-><init>(II[B)V
+Landroid/net/RssiCurve;->lookupScore(IZ)B
+Landroid/net/ScoredNetwork;-><init>(Landroid/net/NetworkKey;Landroid/net/RssiCurve;)V
+Landroid/net/ScoredNetwork;-><init>(Landroid/net/NetworkKey;Landroid/net/RssiCurve;ZLandroid/os/Bundle;)V
+Landroid/net/ScoredNetwork;-><init>(Landroid/net/NetworkKey;Landroid/net/RssiCurve;Z)V
+Landroid/net/SSLCertificateSocketFactory;->castToOpenSSLSocket(Ljava/net/Socket;)Lcom/android/org/conscrypt/OpenSSLSocketImpl;
+Landroid/net/SSLCertificateSocketFactory;->getAlpnSelectedProtocol(Ljava/net/Socket;)[B
+Landroid/net/SSLCertificateSocketFactory;->getDelegate()Ljavax/net/ssl/SSLSocketFactory;
Landroid/net/SSLCertificateSocketFactory;->getHttpSocketFactory(ILandroid/net/SSLSessionCache;)Lorg/apache/http/conn/ssl/SSLSocketFactory;
+Landroid/net/SSLCertificateSocketFactory;-><init>(ILandroid/net/SSLSessionCache;Z)V
+Landroid/net/SSLCertificateSocketFactory;->INSECURE_TRUST_MANAGER:[Ljavax/net/ssl/TrustManager;
+Landroid/net/SSLCertificateSocketFactory;->isSslCheckRelaxed()Z
+Landroid/net/SSLCertificateSocketFactory;->makeSocketFactory([Ljavax/net/ssl/KeyManager;[Ljavax/net/ssl/TrustManager;)Ljavax/net/ssl/SSLSocketFactory;
+Landroid/net/SSLCertificateSocketFactory;->mAlpnProtocols:[B
+Landroid/net/SSLCertificateSocketFactory;->mChannelIdPrivateKey:Ljava/security/PrivateKey;
+Landroid/net/SSLCertificateSocketFactory;->mHandshakeTimeoutMillis:I
+Landroid/net/SSLCertificateSocketFactory;->mInsecureFactory:Ljavax/net/ssl/SSLSocketFactory;
+Landroid/net/SSLCertificateSocketFactory;->mKeyManagers:[Ljavax/net/ssl/KeyManager;
+Landroid/net/SSLCertificateSocketFactory;->mNpnProtocols:[B
+Landroid/net/SSLCertificateSocketFactory;->mSecureFactory:Ljavax/net/ssl/SSLSocketFactory;
+Landroid/net/SSLCertificateSocketFactory;->mSecure:Z
+Landroid/net/SSLCertificateSocketFactory;->mSessionCache:Lcom/android/org/conscrypt/SSLClientSessionCache;
+Landroid/net/SSLCertificateSocketFactory;->mTrustManagers:[Ljavax/net/ssl/TrustManager;
+Landroid/net/SSLCertificateSocketFactory;->setAlpnProtocols([[B)V
+Landroid/net/SSLCertificateSocketFactory;->setChannelIdPrivateKey(Ljava/security/PrivateKey;)V
+Landroid/net/SSLCertificateSocketFactory;->setSoWriteTimeout(Ljava/net/Socket;I)V
+Landroid/net/SSLCertificateSocketFactory;->TAG:Ljava/lang/String;
+Landroid/net/SSLCertificateSocketFactory;->toLengthPrefixedList([[[B)[B
+Landroid/net/SSLCertificateSocketFactory;->verifyHostname(Ljava/net/Socket;Ljava/lang/String;)V
Landroid/net/TrafficStats;->getStatsService()Landroid/net/INetworkStatsService;
+Landroid/net/TrafficStats;->setThreadStatsTagBackup()V
+Landroid/net/TrafficStats;->setThreadStatsTagRestore()V
+Landroid/net/TrafficStats;->setThreadStatsUid(I)V
+Landroid/net/WifiKey;-><init>(Ljava/lang/String;Ljava/lang/String;)V
Landroid/net/wifi/p2p/WifiP2pGroup;->getNetworkId()I
Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/Collection;
Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V
Landroid/net/wifi/p2p/WifiP2pManager;->requestPersistentGroupInfo(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;)V
Landroid/net/wifi/p2p/WifiP2pManager;->setDeviceName(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Ljava/lang/String;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V
+Landroid/net/wifi/RttManager;->getRttCapabilities()Landroid/net/wifi/RttManager$RttCapabilities;
+Landroid/net/wifi/RttManager$RttParams;-><init>()V
+Landroid/net/wifi/RttManager;->startRanging([Landroid/net/wifi/RttManager$RttParams;Landroid/net/wifi/RttManager$RttListener;)V
Landroid/net/wifi/WifiConfiguration;->apBand:I
Landroid/net/wifi/WifiConfiguration;->apChannel:I
Landroid/net/wifi/WifiConfiguration;->hasNoInternetAccess()Z
+Landroid/net/wifi/WifiConfiguration;->isEphemeral()Z
+Landroid/net/wifi/WifiConfiguration;->isNoInternetAccessExpected()Z
Landroid/net/wifi/WifiConfiguration;->mIpConfiguration:Landroid/net/IpConfiguration;
Landroid/net/wifi/WifiConfiguration;->validatedInternetAccess:Z
Landroid/net/wifi/WifiManager;->connect(ILandroid/net/wifi/WifiManager$ActionListener;)V
Landroid/net/wifi/WifiManager;->connect(Landroid/net/wifi/WifiConfiguration;Landroid/net/wifi/WifiManager$ActionListener;)V
Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_STATE:Ljava/lang/String;
Landroid/net/wifi/WifiManager;->forget(ILandroid/net/wifi/WifiManager$ActionListener;)V
+Landroid/net/wifi/WifiManager;->getPrivilegedConfiguredNetworks()Ljava/util/List;
Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;
Landroid/net/wifi/WifiManager;->getWifiApState()I
Landroid/net/wifi/WifiManager;->isDualBandSupported()Z
Landroid/net/wifi/WifiManager;->isWifiApEnabled()Z
+Landroid/net/wifi/WifiManager;->isWifiScannerSupported()Z
Landroid/net/wifi/WifiManager;->mService:Landroid/net/wifi/IWifiManager;
Landroid/net/wifi/WifiManager;->save(Landroid/net/wifi/WifiConfiguration;Landroid/net/wifi/WifiManager$ActionListener;)V
Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z
+Landroid/net/wifi/WifiManager;->startScan(Landroid/os/WorkSource;)Z
Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;
Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I
Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I
Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I
Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I
Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I
+Landroid/net/wifi/WifiScanner;->getScanResults()Z
+Landroid/net/wifi/WifiScanner$ScanData;->getResults()[Landroid/net/wifi/ScanResult;
+Landroid/net/wifi/WifiScanner$ScanSettings;-><init>()V
+Landroid/net/wifi/WifiScanner;->startBackgroundScan(Landroid/net/wifi/WifiScanner$ScanSettings;Landroid/net/wifi/WifiScanner$ScanListener;)V
+Landroid/net/wifi/WifiScanner;->startScan(Landroid/net/wifi/WifiScanner$ScanSettings;Landroid/net/wifi/WifiScanner$ScanListener;Landroid/os/WorkSource;)V
+Landroid/net/wifi/WifiScanner;->startScan(Landroid/net/wifi/WifiScanner$ScanSettings;Landroid/net/wifi/WifiScanner$ScanListener;)V
+Landroid/net/wifi/WifiScanner;->stopBackgroundScan(Landroid/net/wifi/WifiScanner$ScanListener;)V
+Landroid/nfc/NfcAdapter;->addNfcUnlockHandler(Landroid/nfc/NfcAdapter$NfcUnlockHandler;[Ljava/lang/String;)Z
+Landroid/nfc/NfcAdapter;->disable()Z
+Landroid/nfc/NfcAdapter;->enable()Z
Landroid/nfc/NfcAdapter;->getDefaultAdapter()Landroid/nfc/NfcAdapter;
+Landroid/nfc/NfcAdapter;->removeNfcUnlockHandler(Landroid/nfc/NfcAdapter$NfcUnlockHandler;)Z
Landroid/nfc/NfcAdapter;->setNdefPushMessageCallback(Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;Landroid/app/Activity;I)V
+Landroid/nfc/NfcAdapter;->setNdefPushMessage(Landroid/nfc/NdefMessage;Landroid/app/Activity;I)V
Landroid/opengl/GLSurfaceView$EglHelper;->mEglContext:Ljavax/microedition/khronos/egl/EGLContext;
Landroid/opengl/GLSurfaceView$GLThread;->mEglHelper:Landroid/opengl/GLSurfaceView$EglHelper;
Landroid/opengl/GLSurfaceView;->mGLThread:Landroid/opengl/GLSurfaceView$GLThread;
@@ -429,6 +616,7 @@
Landroid/os/AsyncTask;->mWorker:Landroid/os/AsyncTask$WorkerRunnable;
Landroid/os/AsyncTask;->sDefaultExecutor:Ljava/util/concurrent/Executor;
Landroid/os/AsyncTask;->setDefaultExecutor(Ljava/util/concurrent/Executor;)V
+Landroid/os/BatteryStats$HistoryItem;->states2:I
Landroid/os/BatteryStats;->NUM_DATA_CONNECTION_TYPES:I
Landroid/os/BatteryStats$Timer;->getTotalTimeLocked(JI)J
Landroid/os/BatteryStats$Uid;->getFullWifiLockTime(JI)J
@@ -546,27 +734,40 @@
Landroid/os/UpdateEngine;->applyPayload(Ljava/lang/String;JJ[Ljava/lang/String;)V
Landroid/os/UpdateEngine;->bind(Landroid/os/UpdateEngineCallback;Landroid/os/Handler;)Z
Landroid/os/UpdateEngine;->bind(Landroid/os/UpdateEngineCallback;)Z
+Landroid/os/UpdateEngineCallback;-><init>()V
+Landroid/os/UpdateEngineCallback;->onPayloadApplicationComplete(I)V
+Landroid/os/UpdateEngineCallback;->onStatusUpdate(IF)V
Landroid/os/UpdateEngine;->cancel()V
+Landroid/os/UpdateEngine;-><init>()V
Landroid/os/UpdateEngine;->resetStatus()V
Landroid/os/UpdateLock;->acquire()V
Landroid/os/UpdateLock;->isHeld()Z
+Landroid/os/UpdateLock;->NOW_IS_CONVENIENT:Ljava/lang/String;
Landroid/os/UpdateLock;->release()V
+Landroid/os/UpdateLock;->UPDATE_LOCK_CHANGED:Ljava/lang/String;
Landroid/os/UserHandle;->ALL:Landroid/os/UserHandle;
+Landroid/os/UserHandle;->getAppIdFromSharedAppGid(I)I
+Landroid/os/UserHandle;->getCallingUserId()I
Landroid/os/UserHandle;->getIdentifier()I
Landroid/os/UserHandle;->getUserId(I)I
+Landroid/os/UserHandle;-><init>(I)V
Landroid/os/UserHandle;->isOwner()Z
Landroid/os/UserHandle;->myUserId()I
Landroid/os/UserHandle;->of(I)Landroid/os/UserHandle;
+Landroid/os/UserHandle;->USER_OWNER:I
+Landroid/os/UserManager;->getBadgedLabelForUser(Ljava/lang/CharSequence;Landroid/os/UserHandle;)Ljava/lang/CharSequence;
Landroid/os/UserManager;->get(Landroid/content/Context;)Landroid/os/UserManager;
Landroid/os/UserManager;->getMaxSupportedUsers()I
Landroid/os/UserManager;->getProfiles(I)Ljava/util/List;
Landroid/os/UserManager;->getUserHandle()I
Landroid/os/UserManager;->getUserHandle(I)I
Landroid/os/UserManager;->getUserInfo(I)Landroid/content/pm/UserInfo;
+Landroid/os/UserManager;->getUserRestrictionSource(Ljava/lang/String;Landroid/os/UserHandle;)I
Landroid/os/UserManager;->getUserSerialNumber(I)I
Landroid/os/UserManager;->getUsers()Ljava/util/List;
Landroid/os/UserManager;->hasBaseUserRestriction(Ljava/lang/String;Landroid/os/UserHandle;)Z
Landroid/os/UserManager;->isLinkedUser()Z
+Landroid/os/UserManager;->isManagedProfile()Z
Landroid/os/UserManager;->isUserUnlocked(I)Z
Landroid/os/WorkSource;->add(ILjava/lang/String;)Z
Landroid/os/WorkSource;->add(I)Z
@@ -641,10 +842,39 @@
Landroid/service/media/IMediaBrowserServiceCallbacks;->onLoadChildren(Ljava/lang/String;Landroid/content/pm/ParceledListSlice;)V
Landroid/service/media/IMediaBrowserServiceCallbacks;->onLoadChildrenWithOptions(Ljava/lang/String;Landroid/content/pm/ParceledListSlice;Landroid/os/Bundle;)V
Landroid/service/media/MediaBrowserService;->KEY_MEDIA_ITEM:Ljava/lang/String;
+Landroid/service/media/MediaBrowserService$Result;->mFlags:I
Landroid/service/notification/NotificationListenerService;->registerAsSystemService(Landroid/content/Context;Landroid/content/ComponentName;I)V
Landroid/service/notification/NotificationListenerService;->unregisterAsSystemService()V
+Landroid/service/persistentdata/PersistentDataBlockManager;->getMaximumDataBlockSize()J
+Landroid/service/persistentdata/PersistentDataBlockManager;->read()[B
+Landroid/service/persistentdata/PersistentDataBlockManager;->write([B)I
+Landroid/service/resolver/ResolverRankerService;-><init>()V
+Landroid/service/resolver/ResolverTarget;->getChooserScore()F
+Landroid/service/resolver/ResolverTarget;->getLaunchScore()F
+Landroid/service/resolver/ResolverTarget;->getRecencyScore()F
+Landroid/service/resolver/ResolverTarget;->getSelectProbability()F
+Landroid/service/resolver/ResolverTarget;->getTimeSpentScore()F
+Landroid/service/resolver/ResolverTarget;->setSelectProbability(F)V
+Landroid/service/trust/TrustAgentService;-><init>()V
+Landroid/service/voice/VoiceInteractionService;->isKeyphraseAndLocaleSupportedForHotword(Ljava/lang/String;Ljava/util/Locale;)Z
Landroid/service/wallpaper/WallpaperService$Engine;->setFixedSizeAllowed(Z)V
Landroid/speech/tts/TextToSpeech;->getCurrentEngine()Ljava/lang/String;
+Landroid/telecom/AudioState;->getRoute()I
+Landroid/telecom/AudioState;->getSupportedRouteMask()I
+Landroid/telecom/AudioState;->isMuted()Z
+Landroid/telecom/Call;->addListener(Landroid/telecom/Call$Listener;)V
+Landroid/telecom/Call$Listener;-><init>()V
+Landroid/telecom/Call;->removeListener(Landroid/telecom/Call$Listener;)V
+Landroid/telecom/Phone;->addListener(Landroid/telecom/Phone$Listener;)V
+Landroid/telecom/Phone;->getAudioState()Landroid/telecom/AudioState;
+Landroid/telecom/Phone;->getCallAudioState()Landroid/telecom/CallAudioState;
+Landroid/telecom/Phone;->getCalls()Ljava/util/List;
+Landroid/telecom/Phone$Listener;-><init>()V
+Landroid/telecom/Phone;->removeListener(Landroid/telecom/Phone$Listener;)V
+Landroid/telecom/Phone;->setAudioRoute(I)V
+Landroid/telecom/Phone;->setMuted(Z)V
+Landroid/telecom/TelecomManager;->endCall()Z
+Landroid/telecom/TelecomManager;->EXTRA_IS_HANDOVER:Ljava/lang/String;
Landroid/telephony/PhoneStateListener;->mSubId:Ljava/lang/Integer;
Landroid/telephony/ServiceState;->newFromBundle(Landroid/os/Bundle;)Landroid/telephony/ServiceState;
Landroid/telephony/SignalStrength;->getCdmaLevel()I
@@ -657,10 +887,12 @@
Landroid/telephony/SmsMessage;->mWrappedSmsMessage:Lcom/android/internal/telephony/SmsMessageBase;
Landroid/telephony/SubscriptionManager;->getDefaultSmsPhoneId()I
Landroid/telephony/SubscriptionManager;->getPhoneId(I)I
+Landroid/telephony/SubscriptionManager;->getSlotIndex(I)I
Landroid/telephony/SubscriptionManager;->getSubId(I)[I
Landroid/telephony/SubscriptionManager;->setDefaultSmsSubId(I)V
Landroid/telephony/TelephonyManager;->checkCarrierPrivilegesForPackage(Ljava/lang/String;)I
Landroid/telephony/TelephonyManager;->from(Landroid/content/Context;)Landroid/telephony/TelephonyManager;
+Landroid/telephony/TelephonyManager;->getCarrierPackageNamesForIntent(Landroid/content/Intent;)Ljava/util/List;
Landroid/telephony/TelephonyManager;->getCurrentPhoneType()I
Landroid/telephony/TelephonyManager;->getCurrentPhoneType(I)I
Landroid/telephony/TelephonyManager;->getDataEnabled(I)Z
@@ -691,6 +923,36 @@
Landroid/text/SpannableStringBuilder;->mSpanFlags:[I
Landroid/text/SpannableStringBuilder;->mSpans:[Ljava/lang/Object;
Landroid/text/SpannableStringBuilder;->mSpanStarts:[I
+Landroid/text/SpannableStringInternal;->charAt(I)C
+Landroid/text/SpannableStringInternal;->checkRange(Ljava/lang/String;II)V
+Landroid/text/SpannableStringInternal;->COLUMNS:I
+Landroid/text/SpannableStringInternal;->copySpans(Landroid/text/SpannableStringInternal;II)V
+Landroid/text/SpannableStringInternal;->copySpans(Landroid/text/Spanned;II)V
+Landroid/text/SpannableStringInternal;->EMPTY:[Ljava/lang/Object;
+Landroid/text/SpannableStringInternal;->END:I
+Landroid/text/SpannableStringInternal;->FLAGS:I
+Landroid/text/SpannableStringInternal;->getChars(II[CI)V
+Landroid/text/SpannableStringInternal;->getSpanEnd(Ljava/lang/Object;)I
+Landroid/text/SpannableStringInternal;->getSpanFlags(Ljava/lang/Object;)I
+Landroid/text/SpannableStringInternal;->getSpans(IILjava/lang/Class;)[Ljava/lang/Object;
+Landroid/text/SpannableStringInternal;->getSpanStart(Ljava/lang/Object;)I
+Landroid/text/SpannableStringInternal;-><init>(Ljava/lang/CharSequence;II)V
+Landroid/text/SpannableStringInternal;->isIndexFollowsNextLine(I)Z
+Landroid/text/SpannableStringInternal;->isOutOfCopyRange(IIII)Z
+Landroid/text/SpannableStringInternal;->length()I
+Landroid/text/SpannableStringInternal;->mSpanCount:I
+Landroid/text/SpannableStringInternal;->mSpanData:[I
+Landroid/text/SpannableStringInternal;->mSpans:[Ljava/lang/Object;
+Landroid/text/SpannableStringInternal;->mText:Ljava/lang/String;
+Landroid/text/SpannableStringInternal;->nextSpanTransition(IILjava/lang/Class;)I
+Landroid/text/SpannableStringInternal;->region(II)Ljava/lang/String;
+Landroid/text/SpannableStringInternal;->removeSpan(Ljava/lang/Object;)V
+Landroid/text/SpannableStringInternal;->sendSpanAdded(Ljava/lang/Object;II)V
+Landroid/text/SpannableStringInternal;->sendSpanChanged(Ljava/lang/Object;IIII)V
+Landroid/text/SpannableStringInternal;->sendSpanRemoved(Ljava/lang/Object;II)V
+Landroid/text/SpannableStringInternal;->setSpan(Ljava/lang/Object;III)V
+Landroid/text/SpannableStringInternal;->setSpan(Ljava/lang/Object;IIIZ)V
+Landroid/text/SpannableStringInternal;->START:I
Landroid/text/StaticLayout;->mColumns:I
Landroid/text/StaticLayout;->mLineCount:I
Landroid/text/StaticLayout;->mLines:[I
@@ -732,6 +994,8 @@
Landroid/view/IWindowManager;->setStrictModeVisualIndicatorPreference(Ljava/lang/String;)V
Landroid/view/IWindowManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/view/IWindowManager;
Landroid/view/IWindowSession$Stub$Proxy;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/view/DisplayCutout$ParcelableWrapper;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
+Landroid/view/LayoutInflater;->createViewFromTag(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/view/View;
+Landroid/view/LayoutInflater;->createViewFromTag(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;Z)Landroid/view/View;
Landroid/view/LayoutInflater;->mConstructorArgs:[Ljava/lang/Object;
Landroid/view/LayoutInflater;->mFactory2:Landroid/view/LayoutInflater$Factory2;
Landroid/view/LayoutInflater;->mFactory:Landroid/view/LayoutInflater$Factory;
@@ -1003,9 +1267,27 @@
Landroid/widget/VideoView;->mVideoWidth:I
Lcom/android/internal/app/IBatteryStats;->getStatistics()[B
Lcom/android/internal/app/IBatteryStats$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IBatteryStats;
+Lcom/android/internal/os/BatterySipper;->add(Lcom/android/internal/os/BatterySipper;)V
+Lcom/android/internal/os/BatterySipper;->drainType:Lcom/android/internal/os/BatterySipper$DrainType;
+Lcom/android/internal/os/BatterySipper;->getUid()I
+Lcom/android/internal/os/BatterySipper;-><init>(Lcom/android/internal/os/BatterySipper$DrainType;Landroid/os/BatteryStats$Uid;D)V
+Lcom/android/internal/os/BatterySipper;->mPackages:[Ljava/lang/String;
+Lcom/android/internal/os/BatterySipper;->packageWithHighestDrain:Ljava/lang/String;
+Lcom/android/internal/os/BatterySipper;->totalPowerMah:D
+Lcom/android/internal/os/BatterySipper;->uidObj:Landroid/os/BatteryStats$Uid;
+Lcom/android/internal/os/BatteryStatsHelper;->getMaxPower()D
+Lcom/android/internal/os/BatteryStatsHelper;->getStats()Landroid/os/BatteryStats;
+Lcom/android/internal/os/BatteryStatsHelper;->getTotalPower()D
+Lcom/android/internal/os/BatteryStatsHelper;-><init>(Landroid/content/Context;ZZ)V
+Lcom/android/internal/os/BatteryStatsHelper;->load()V
+Lcom/android/internal/os/BatteryStatsHelper;->mBatteryInfo:Lcom/android/internal/app/IBatteryStats;
+Lcom/android/internal/os/BatteryStatsHelper;->mPowerProfile:Lcom/android/internal/os/PowerProfile;
+Lcom/android/internal/os/BatteryStatsHelper;->mUsageList:Ljava/util/List;
+Lcom/android/internal/os/BatteryStatsHelper;->refreshStats(II)V
Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryRealtime(JI)J
Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryUptime(JI)J
Lcom/android/internal/os/BatteryStatsImpl;->CREATOR:Landroid/os/Parcelable$Creator;
+Lcom/android/internal/os/BatteryStatsImpl;->getDischargeAmount(I)I
Lcom/android/internal/os/BatteryStatsImpl;->getGlobalWifiRunningTime(JI)J
Lcom/android/internal/os/BatteryStatsImpl;->getScreenOnTime(JI)J
Lcom/android/internal/os/BatteryStatsImpl;->getUidStats()Landroid/util/SparseArray;
@@ -1091,6 +1373,9 @@
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([B)V
Ldalvik/system/BaseDexClassLoader;->getLdLibraryPath()Ljava/lang/String;
Ldalvik/system/BaseDexClassLoader;->pathList:Ldalvik/system/DexPathList;
+Ldalvik/system/BlockGuard;->getThreadPolicy()Ldalvik/system/BlockGuard$Policy;
+Ldalvik/system/BlockGuard$Policy;->onNetwork()V
+Ldalvik/system/CloseGuard;->close()V
Ldalvik/system/CloseGuard;->get()Ldalvik/system/CloseGuard;
Ldalvik/system/CloseGuard;->open(Ljava/lang/String;)V
Ldalvik/system/CloseGuard;->warnIfOpen()V
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0bc510a..aca8d48 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6362,6 +6362,8 @@
} else {
writer.print(prefix); writer.println("No AutofillManager");
}
+
+ ResourcesManager.getInstance().dump(prefix, writer);
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3d088ff..5a63319 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -113,6 +113,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
+import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -166,9 +167,9 @@
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.text.DateFormat;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -204,7 +205,7 @@
private static final boolean DEBUG_SERVICE = false;
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
- private static final boolean DEBUG_ORDER = false;
+ public static final boolean DEBUG_ORDER = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
@@ -222,7 +223,7 @@
private static final boolean REPORT_TO_ACTIVITY = true;
// Maximum number of recent tokens to maintain for debugging purposes
- private static final int MAX_RECENT_TOKENS = 10;
+ private static final int MAX_DESTROYED_ACTIVITIES = 10;
/**
* Denotes an invalid sequence number corresponding to a process state change.
@@ -256,7 +257,7 @@
final H mH = new H();
final Executor mExecutor = new HandlerExecutor(mH);
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
- final ArrayDeque<Integer> mRecentTokens = new ArrayDeque<>();
+ final ArrayList<DestroyedActivityInfo> mRecentDestroyedActivities = new ArrayList<>();
// List of new activities (via ActivityRecord.nextIdle) that should
// be reported when next we idle.
@@ -339,6 +340,26 @@
}
}
+ /**
+ * TODO(b/71506345): Remove this once bug is resolved.
+ */
+ private static final class DestroyedActivityInfo {
+ private final Integer mToken;
+ private final String mReason;
+ private final long mTime;
+
+ DestroyedActivityInfo(Integer token, String reason) {
+ mToken = token;
+ mReason = reason;
+ mTime = System.currentTimeMillis();
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "[token:" + mToken + " | time:" + mTime + " | reason:" + mReason
+ + "]");
+ }
+ }
+
// The lock of mProviderMap protects the following variables.
final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
= new ArrayMap<ProviderKey, ProviderClientRecord>();
@@ -398,7 +419,6 @@
boolean startsNotResumed;
public final boolean isForward;
int pendingConfigChanges;
- boolean onlyLocalRequest;
Window mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -520,7 +540,6 @@
sb.append(", startsNotResumed=").append(startsNotResumed);
sb.append(", isForward=").append(isForward);
sb.append(", pendingConfigChanges=").append(pendingConfigChanges);
- sb.append(", onlyLocalRequest=").append(onlyLocalRequest);
sb.append(", preserveWindow=").append(mPreserveWindow);
if (activity != null) {
sb.append(", Activity{");
@@ -765,15 +784,6 @@
sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
}
- @Override
- public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config,
- Configuration overrideConfig, boolean preserveWindow) {
- requestRelaunchActivity(token, pendingResults, pendingNewIntents,
- configChanges, notResumed, config, overrideConfig, true, preserveWindow);
- }
-
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean sync, int sendingUser, int processState) {
@@ -1531,7 +1541,6 @@
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
- public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
public static final int DESTROY_BACKUP_AGENT = 129;
@@ -1577,7 +1586,6 @@
case UNBIND_SERVICE: return "UNBIND_SERVICE";
case DUMP_SERVICE: return "DUMP_SERVICE";
case LOW_MEMORY: return "LOW_MEMORY";
- case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
@@ -1611,12 +1619,6 @@
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
- case RELAUNCH_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
- ActivityClientRecord r = (ActivityClientRecord)msg.obj;
- handleRelaunchActivity(r);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
@@ -2182,14 +2184,28 @@
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "mActivities:");
+ pw.println(prefix + "Activities:");
- for (ArrayMap.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
- pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:"
- + entry.getValue().toString() + "]");
+ if (!mActivities.isEmpty()) {
+ final Iterator<Map.Entry<IBinder, ActivityClientRecord>> activitiesIterator =
+ mActivities.entrySet().iterator();
+
+ while (activitiesIterator.hasNext()) {
+ final ArrayMap.Entry<IBinder, ActivityClientRecord> entry =
+ activitiesIterator.next();
+ pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:"
+ + entry.getValue().toString() + "]");
+ }
}
- pw.println(prefix + "mRecentTokens:" + mRecentTokens);
+ if (!mRecentDestroyedActivities.isEmpty()) {
+ pw.println(prefix + "Recent destroyed activities:");
+ for (int i = 0, size = mRecentDestroyedActivities.size(); i < size; i++) {
+ final DestroyedActivityInfo info = mRecentDestroyedActivities.get(i);
+ pw.print(prefix);
+ info.dump(pw, " ");
+ }
+ }
}
public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
@@ -2876,11 +2892,6 @@
r.setState(ON_CREATE);
mActivities.put(r.token, r);
- mRecentTokens.push(r.token.hashCode());
-
- if (mRecentTokens.size() > MAX_RECENT_TOKENS) {
- mRecentTokens.removeLast();
- }
} catch (SuperNotCalledException e) {
throw e;
@@ -3726,20 +3737,6 @@
}
r.activity.performResume(r.startsNotResumed);
- synchronized (mResourcesManager) {
- // If there is a pending local relaunch that was requested when the activity was
- // paused, it will put the activity into paused state when it finally happens.
- // Since the activity resumed before being relaunched, we don't want that to
- // happen, so we need to clear the request to relaunch paused.
- for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
- final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
- if (relaunching.token == r.token
- && relaunching.onlyLocalRequest && relaunching.startsNotResumed) {
- relaunching.startsNotResumed = false;
- }
- }
- }
-
EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName(), reason);
@@ -3888,14 +3885,12 @@
}
}
- if (!r.onlyLocalRequest) {
- r.nextIdle = mNewActivities;
- mNewActivities = r;
- if (localLOGV) Slog.v(
- TAG, "Scheduling idle handler for " + r);
- Looper.myQueue().addIdleHandler(new Idler());
+ r.nextIdle = mNewActivities;
+ mNewActivities = r;
+ if (localLOGV) {
+ Slog.v(TAG, "Scheduling idle handler for " + r);
}
- r.onlyLocalRequest = false;
+ Looper.myQueue().addIdleHandler(new Idler());
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
@@ -4453,7 +4448,7 @@
/** Core implementation of activity destroy call. */
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
+ int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
Class<? extends Activity> activityClass = null;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
@@ -4505,6 +4500,12 @@
r.setState(ON_DESTROY);
}
mActivities.remove(token);
+ mRecentDestroyedActivities.add(0, new DestroyedActivityInfo(token.hashCode(), reason));
+
+ final int recentDestroyedActivitiesSize = mRecentDestroyedActivities.size();
+ if (recentDestroyedActivitiesSize > MAX_DESTROYED_ACTIVITIES) {
+ mRecentDestroyedActivities.remove(recentDestroyedActivitiesSize - 1);
+ }
StrictMode.decrementExpectedActivityCount(activityClass);
return r;
}
@@ -4516,9 +4517,9 @@
@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
- boolean getNonConfigInstance) {
+ boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
- configChanges, getNonConfigInstance);
+ configChanges, getNonConfigInstance, reason);
if (r != null) {
cleanUpPendingRemoveWindows(r, finishing);
WindowManager wm = r.activity.getWindowManager();
@@ -4586,15 +4587,12 @@
mSomeActivitiesChanged = true;
}
- /**
- * @param preserveWindow Whether the activity should try to reuse the window it created,
- * including the decor view after the relaunch.
- */
- public final void requestRelaunchActivity(IBinder token,
+ @Override
+ public ActivityClientRecord prepareRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config,
- Configuration overrideConfig, boolean fromServer, boolean preserveWindow) {
+ int configChanges, MergedConfiguration config, boolean preserveWindow) {
ActivityClientRecord target = null;
+ boolean scheduleRelaunch = false;
synchronized (mResourcesManager) {
for (int i=0; i<mRelaunchingActivities.size(); i++) {
@@ -4616,57 +4614,31 @@
r.pendingIntents = pendingNewIntents;
}
}
-
- // For each relaunch request, activity manager expects an answer
- if (!r.onlyLocalRequest && fromServer) {
- try {
- ActivityManager.getService().activityRelaunched(token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
break;
}
}
if (target == null) {
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null, fromServer:"
- + fromServer);
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null");
target = new ActivityClientRecord();
target.token = token;
target.pendingResults = pendingResults;
target.pendingIntents = pendingNewIntents;
target.mPreserveWindow = preserveWindow;
- if (!fromServer) {
- final ActivityClientRecord existing = mActivities.get(token);
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + existing);
- if (existing != null) {
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: paused= "
- + existing.paused);;
- target.startsNotResumed = existing.paused;
- target.overrideConfig = existing.overrideConfig;
- }
- target.onlyLocalRequest = true;
- }
mRelaunchingActivities.add(target);
- sendMessage(H.RELAUNCH_ACTIVITY, target);
+ scheduleRelaunch = true;
}
-
- if (fromServer) {
- target.startsNotResumed = notResumed;
- target.onlyLocalRequest = false;
- }
- if (config != null) {
- target.createdConfig = config;
- }
- if (overrideConfig != null) {
- target.overrideConfig = overrideConfig;
- }
+ target.createdConfig = config.getGlobalConfiguration();
+ target.overrideConfig = config.getOverrideConfiguration();
target.pendingConfigChanges |= configChanges;
}
+
+ return scheduleRelaunch ? target : null;
}
- private void handleRelaunchActivity(ActivityClientRecord tmp) {
+ @Override
+ public void handleRelaunchActivity(ActivityClientRecord tmp,
+ PendingTransactionActions pendingActions) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
@@ -4735,18 +4707,10 @@
ActivityClientRecord r = mActivities.get(tmp.token);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
if (r == null) {
- if (!tmp.onlyLocalRequest) {
- try {
- ActivityManager.getService().activityRelaunched(tmp.token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
return;
}
r.activity.mConfigChangeFlags |= configChanges;
- r.onlyLocalRequest = tmp.onlyLocalRequest;
r.mPreserveWindow = tmp.mPreserveWindow;
r.activity.mChangingConfigurations = true;
@@ -4763,9 +4727,9 @@
// preserved by the server, so we want to notify it that we are preparing to replace
// everything
try {
- if (r.mPreserveWindow || r.onlyLocalRequest) {
+ if (r.mPreserveWindow) {
WindowManagerGlobal.getWindowSession().prepareToReplaceWindows(
- r.token, !r.onlyLocalRequest);
+ r.token, true /* childrenOnly */);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -4780,7 +4744,7 @@
callActivityOnStop(r, true /* saveState */, "handleRelaunchActivity");
}
- handleDestroyActivity(r.token, false, configChanges, true);
+ handleDestroyActivity(r.token, false, configChanges, true, "handleRelaunchActivity");
r.activity = null;
r.window = null;
@@ -4804,24 +4768,22 @@
r.startsNotResumed = tmp.startsNotResumed;
r.overrideConfig = tmp.overrideConfig;
- // TODO(lifecycler): Move relaunch to lifecycler.
- PendingTransactionActions pendingActions = new PendingTransactionActions();
handleLaunchActivity(r, pendingActions);
- handleStartActivity(r, pendingActions);
- handleResumeActivity(r.token, false /* clearHide */, r.isForward, "relaunch");
- if (r.startsNotResumed) {
- performPauseActivity(r, false /* finished */, "relaunch", pendingActions);
- }
+ // Only report a successful relaunch to WindowManager.
+ pendingActions.setReportRelaunchToWindowManager(true);
+ }
- if (!tmp.onlyLocalRequest) {
- try {
- ActivityManager.getService().activityRelaunched(r.token);
- if (r.window != null) {
- r.window.reportActivityRelaunched();
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ @Override
+ public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
+ try {
+ ActivityManager.getService().activityRelaunched(token);
+ final ActivityClientRecord r = mActivities.get(token);
+ if (pendingActions.shouldReportRelaunchToWindowManager() && r != null
+ && r.window != null) {
+ r.window.reportActivityRelaunched();
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -5890,21 +5852,23 @@
// Preload fonts resources
FontsContract.setApplicationContextForResources(appContext);
- try {
- final ApplicationInfo info =
- getPackageManager().getApplicationInfo(
- data.appInfo.packageName,
- PackageManager.GET_META_DATA /*flags*/,
- UserHandle.myUserId());
- if (info.metaData != null) {
- final int preloadedFontsResource = info.metaData.getInt(
- ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
- if (preloadedFontsResource != 0) {
- data.info.getResources().preloadFonts(preloadedFontsResource);
+ if (!Process.isIsolated()) {
+ try {
+ final ApplicationInfo info =
+ getPackageManager().getApplicationInfo(
+ data.appInfo.packageName,
+ PackageManager.GET_META_DATA /*flags*/,
+ UserHandle.myUserId());
+ if (info.metaData != null) {
+ final int preloadedFontsResource = info.metaData.getInt(
+ ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
+ if (preloadedFontsResource != 0) {
+ data.info.getResources().preloadFonts(preloadedFontsResource);
+ }
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 5b61fdf..310965e 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -21,6 +21,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
+import android.util.MergedConfiguration;
import com.android.internal.content.ReferrerIntent;
@@ -60,7 +61,7 @@
/** Destroy the activity. */
public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
- boolean getNonConfigInstance);
+ boolean getNonConfigInstance, String reason);
/** Pause the activity. */
public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
@@ -124,6 +125,39 @@
public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
/**
+ * Prepare activity relaunch to update internal bookkeeping. This is used to track multiple
+ * relaunch and config update requests.
+ * @param token Activity token.
+ * @param pendingResults Activity results to be delivered.
+ * @param pendingNewIntents New intent messages to be delivered.
+ * @param configChanges Mask of configuration changes that have occurred.
+ * @param config New configuration applied to the activity.
+ * @param preserveWindow Whether the activity should try to reuse the window it created,
+ * including the decor view after the relaunch.
+ * @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
+ * relaunch, or {@code null} if relaunch cancelled.
+ */
+ public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ int configChanges, MergedConfiguration config, boolean preserveWindow);
+
+ /**
+ * Perform activity relaunch.
+ * @param r Activity client record prepared for relaunch.
+ * @param pendingActions Pending actions to be used on later stages of activity transaction.
+ * */
+ public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r,
+ PendingTransactionActions pendingActions);
+
+ /**
+ * Report that relaunch request was handled.
+ * @param token Target activity token.
+ * @param pendingActions Pending actions initialized on earlier stages of activity transaction.
+ * Used to check if we should report relaunch to WM.
+ * */
+ public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions);
+
+ /**
* Debugging output.
* @param pw {@link PrintWriter} to write logs to.
* @param prefix Prefix to prepend to output.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4a9b2bc..a1ba13d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -409,6 +409,7 @@
return sp;
}
+ @GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 2b648ea..eb26026 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -609,18 +609,19 @@
/**
* A key was pressed down.
+ * <p>
+ * If the focused view didn't want this event, this method is called.
+ * <p>
+ * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
+ * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
+ * KEYCODE_ESCAPE} to later handle them in {@link #onKeyUp}.
*
- * <p>If the focused view didn't want this event, this method is called.
- *
- * <p>The default implementation consumed the KEYCODE_BACK to later
- * handle it in {@link #onKeyUp}.
- *
* @see #onKeyUp
* @see android.view.KeyEvent
*/
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
event.startTracking();
return true;
}
@@ -640,16 +641,18 @@
/**
* A key was released.
- *
- * <p>The default implementation handles KEYCODE_BACK to close the
- * dialog.
+ * <p>
+ * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
+ * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
+ * KEYCODE_ESCAPE} to close the dialog.
*
* @see #onKeyDown
- * @see KeyEvent
+ * @see android.view.KeyEvent
*/
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+ if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
+ && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 9e99a78..ae9b83e 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -83,9 +83,6 @@
int resultCode, in String data, in Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState);
void scheduleLowMemory();
- void scheduleRelaunchActivity(IBinder token, in List<ResultInfo> pendingResults,
- in List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
- in Configuration config, in Configuration overrideConfig, boolean preserveWindow);
void scheduleSleeping(IBinder token, boolean sleeping);
void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
void setSchedulingGroup(int group);
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 76a3682..2ba4c00 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -88,7 +88,7 @@
public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix,
String token, InstantAppResolutionCallback callback) {
// if not overridden, forward to old methods and filter out non-web intents
- if (sanitizedIntent.isBrowsableWebIntent()) {
+ if (sanitizedIntent.isWebIntent()) {
onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
} else {
callback.onInstantAppResolveInfo(Collections.emptyList());
@@ -107,7 +107,7 @@
String token, InstantAppResolutionCallback callback) {
Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
// if not overridden, forward to old methods and filter out non-web intents
- if (sanitizedIntent.isBrowsableWebIntent()) {
+ if (sanitizedIntent.isWebIntent()) {
onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
} else {
callback.onInstantAppResolveInfo(Collections.emptyList());
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 1d34595..e297719 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -380,7 +380,7 @@
}
if (localLOGV) Log.v(TAG, r.id + ": destroying");
mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */,
- false /* getNonConfigInstance */);
+ false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
r.activity = null;
r.window = null;
if (finish) {
@@ -645,7 +645,7 @@
LocalActivityRecord r = mActivityArray.get(i);
if (localLOGV) Log.v(TAG, r.id + ": destroying");
mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */,
- false /* getNonConfigInstance */);
+ false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
}
mActivities.clear();
mActivityArray.clear();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6e40986..c805658 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1055,11 +1055,10 @@
/**
* {@link #extras} key: A
* {@link android.content.ContentUris content URI} pointing to an image that can be displayed
- * in the background when the notification is selected. The URI must point to an image stream
- * suitable for passing into
+ * in the background when the notification is selected. Used on television platforms.
+ * The URI must point to an image stream suitable for passing into
* {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
- * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
- * URI used for this purpose must require no permissions to read the image data.
+ * BitmapFactory.decodeStream}; all other content types will be ignored.
*/
public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
@@ -4617,10 +4616,15 @@
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
Action action = mActions.get(i);
- validRemoteInput |= hasValidRemoteInput(action);
+ boolean actionHasValidInput = hasValidRemoteInput(action);
+ validRemoteInput |= actionHasValidInput;
final RemoteViews button = generateActionButton(action, emphazisedMode,
i % 2 != 0, p.ambient);
+ if (actionHasValidInput) {
+ // Clear the drawable
+ button.setInt(R.id.action0, "setBackgroundResource", 0);
+ }
big.addView(R.id.actions, button);
}
} else {
@@ -6435,7 +6439,7 @@
public RemoteViews makeContentView(boolean increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
+ RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
return remoteViews;
@@ -6470,11 +6474,11 @@
*/
@Override
public RemoteViews makeBigContentView() {
- return makeBigContentView(false /* showRightIcon */);
+ return makeMessagingView(false /* isCollapsed */);
}
@NonNull
- private RemoteViews makeBigContentView(boolean showRightIcon) {
+ private RemoteViews makeMessagingView(boolean isCollapsed) {
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
@@ -6485,21 +6489,24 @@
nameReplacement = conversationTitle;
conversationTitle = null;
}
+ boolean hideLargeIcon = !isCollapsed || isOneToOne;
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
- .hideLargeIcon(!showRightIcon || isOneToOne)
+ .hideLargeIcon(hideLargeIcon)
.headerTextSecondary(conversationTitle)
- .alwaysShowReply(showRightIcon));
+ .alwaysShowReply(isCollapsed));
addExtras(mBuilder.mN.extras);
// also update the end margin if there is an image
int endMargin = R.dimen.notification_content_margin_end;
- if (mBuilder.mN.hasLargeIcon() && showRightIcon) {
+ if (isCollapsed) {
endMargin = R.dimen.notification_content_plus_picture_margin_end;
}
contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
mBuilder.resolveContrastColor());
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
+ isCollapsed);
contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
mBuilder.mN.mLargeIcon);
contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
@@ -6566,7 +6573,7 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
+ RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
return remoteViews;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 538623f..b10e608 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -24,6 +24,7 @@
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -350,6 +351,14 @@
* the same tag and id has already been posted by your application and has not yet been
* canceled, it will be replaced by the updated information.
*
+ * All {@link android.service.notification.NotificationListenerService listener services} will
+ * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris}
+ * provided on this notification or the
+ * {@link NotificationChannel} this notification is posted to using
+ * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the
+ * notification is canceled, or you can revoke permissions with
+ * {@link Context#revokeUriPermission(Uri, int)}.
+ *
* @param tag A string identifier for this notification. May be {@code null}.
* @param id An identifier for this notification. The pair (tag, id) must be unique
* within your application.
@@ -370,11 +379,13 @@
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
+
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
+
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -385,6 +396,7 @@
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
notification.reduceImageSizes(mContext);
+
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
boolean isLowRam = am.isLowRamDevice();
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index fb11272..b96e028 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.CompatResources;
import android.content.res.CompatibilityInfo;
@@ -34,6 +35,7 @@
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.LruCache;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;
@@ -41,9 +43,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -59,12 +65,7 @@
* Predicate that returns true if a WeakReference is gc'ed.
*/
private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
- new Predicate<WeakReference<Resources>>() {
- @Override
- public boolean test(WeakReference<Resources> weakRef) {
- return weakRef == null || weakRef.get() == null;
- }
- };
+ weakRef -> weakRef == null || weakRef.get() == null;
/**
* The global compatibility settings.
@@ -89,6 +90,48 @@
*/
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
+ private static class ApkKey {
+ public final String path;
+ public final boolean sharedLib;
+ public final boolean overlay;
+
+ ApkKey(String path, boolean sharedLib, boolean overlay) {
+ this.path = path;
+ this.sharedLib = sharedLib;
+ this.overlay = overlay;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + this.path.hashCode();
+ result = 31 * result + Boolean.hashCode(this.sharedLib);
+ result = 31 * result + Boolean.hashCode(this.overlay);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ApkKey)) {
+ return false;
+ }
+ ApkKey other = (ApkKey) obj;
+ return this.path.equals(other.path) && this.sharedLib == other.sharedLib
+ && this.overlay == other.overlay;
+ }
+ }
+
+ /**
+ * The ApkAssets we are caching and intend to hold strong references to.
+ */
+ private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15);
+
+ /**
+ * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
+ * in our LRU cache. Bonus resources :)
+ */
+ private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
+
/**
* Resources and base configuration override associated with an Activity.
*/
@@ -260,6 +303,41 @@
}
}
+ private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
+ throws IOException {
+ final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
+ ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
+ if (apkAssets != null) {
+ return apkAssets;
+ }
+
+ // Optimistically check if this ApkAssets exists somewhere else.
+ final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
+ if (apkAssetsRef != null) {
+ apkAssets = apkAssetsRef.get();
+ if (apkAssets != null) {
+ mLoadedApkAssets.put(newKey, apkAssets);
+ return apkAssets;
+ } else {
+ // Clean up the reference.
+ mCachedApkAssets.remove(newKey);
+ }
+ }
+
+ // We must load this from disk.
+ if (overlay) {
+ final String idmapPath = "/data/resource-cache/"
+ + path.substring(1).replace('/', '@')
+ + "@idmap";
+ apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
+ } else {
+ apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
+ }
+ mLoadedApkAssets.put(newKey, apkAssets);
+ mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
+ return apkAssets;
+ }
+
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*
@@ -270,13 +348,15 @@
*/
@VisibleForTesting
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
- AssetManager assets = new AssetManager();
+ final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (key.mResDir != null) {
- if (assets.addAssetPath(key.mResDir) == 0) {
+ try {
+ apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/));
+ } catch (IOException e) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
@@ -284,7 +364,10 @@
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
- if (assets.addAssetPath(splitResDir) == 0) {
+ try {
+ apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/,
+ false /*overlay*/));
+ } catch (IOException e) {
Log.e(TAG, "failed to add split asset path " + splitResDir);
return null;
}
@@ -293,7 +376,13 @@
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
- assets.addOverlayPath(idmapPath);
+ try {
+ apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/));
+ } catch (IOException e) {
+ Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+ // continue.
+ }
}
}
@@ -302,16 +391,77 @@
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
- if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
+ try {
+ apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/));
+ } catch (IOException e) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
+
+ // continue.
}
}
}
}
+
+ AssetManager assets = new AssetManager();
+ assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]),
+ false /*invalidateCaches*/);
return assets;
}
+ private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
+ int count = 0;
+ for (WeakReference<T> ref : collection) {
+ final T value = ref != null ? ref.get() : null;
+ if (value != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * @hide
+ */
+ public void dump(String prefix, PrintWriter printWriter) {
+ synchronized (this) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ for (int i = 0; i < prefix.length() / 2; i++) {
+ pw.increaseIndent();
+ }
+
+ pw.println("ResourcesManager:");
+ pw.increaseIndent();
+ pw.print("cached apks: total=");
+ pw.print(mLoadedApkAssets.size());
+ pw.print(" created=");
+ pw.print(mLoadedApkAssets.createCount());
+ pw.print(" evicted=");
+ pw.print(mLoadedApkAssets.evictionCount());
+ pw.print(" hit=");
+ pw.print(mLoadedApkAssets.hitCount());
+ pw.print(" miss=");
+ pw.print(mLoadedApkAssets.missCount());
+ pw.print(" max=");
+ pw.print(mLoadedApkAssets.maxSize());
+ pw.println();
+
+ pw.print("total apks: ");
+ pw.println(countLiveReferences(mCachedApkAssets.values()));
+
+ pw.print("resources: ");
+
+ int references = countLiveReferences(mResourceReferences);
+ for (ActivityResources activityResources : mActivityResourceReferences.values()) {
+ references += countLiveReferences(activityResources.activityResources);
+ }
+ pw.println(references);
+
+ pw.print("resource impls: ");
+ pw.println(countLiveReferences(mResourceImpls.values()));
+ }
+ }
+
private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
Configuration config;
final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
@@ -630,28 +780,16 @@
// We will create the ResourcesImpl object outside of holding this lock.
}
- }
- // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
- ResourcesImpl resourcesImpl = createResourcesImpl(key);
- if (resourcesImpl == null) {
- return null;
- }
-
- synchronized (this) {
- ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
- if (existingResourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
- + " new impl=" + resourcesImpl);
- }
- resourcesImpl.getAssets().close();
- resourcesImpl = existingResourcesImpl;
- } else {
- // Add this ResourcesImpl to the cache.
- mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+ // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
+ ResourcesImpl resourcesImpl = createResourcesImpl(key);
+ if (resourcesImpl == null) {
+ return null;
}
+ // Add this ResourcesImpl to the cache.
+ mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 256c479..ea0fd75 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -471,14 +471,6 @@
* {@link #onStart} and returns either {@link #START_STICKY}
* or {@link #START_STICKY_COMPATIBILITY}.
*
- * <p>If you need your application to run on platform versions prior to API
- * level 5, you can use the following model to handle the older {@link #onStart}
- * callback in that case. The <code>handleCommand</code> method is implemented by
- * you as appropriate:
- *
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
- * start_compatibility}
- *
* <p class="caution">Note that the system calls this on your
* service's main thread. A service's main thread is the same
* thread where UI operations take place for Activities running in the
@@ -687,6 +679,10 @@
* {@link #startService(Intent)} first to tell the system it should keep the service running,
* and then use this method to tell it to keep it running harder.</p>
*
+ * <p>Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
+ * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
+ * this API.</p>
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}; must not be 0.
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index c525c89c..c2c91c2 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -30,20 +30,27 @@
* @hide
*/
@SystemApi
-public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends.
+public final class StatsManager {
IStatsManager mService;
private static final String TAG = "StatsManager";
+ private static final boolean DEBUG = false;
- /** Long extra of uid that added the relevant stats config. */
- public static final String EXTRA_STATS_CONFIG_UID =
- "android.app.extra.STATS_CONFIG_UID";
- /** Long extra of the relevant stats config's configKey. */
- public static final String EXTRA_STATS_CONFIG_KEY =
- "android.app.extra.STATS_CONFIG_KEY";
- /** Long extra of the relevant statsd_config.proto's Subscription.id. */
+ /**
+ * Long extra of uid that added the relevant stats config.
+ */
+ public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
+ /**
+ * Long extra of the relevant stats config's configKey.
+ */
+ public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
+ /**
+ * Long extra of the relevant statsd_config.proto's Subscription.id.
+ */
public static final String EXTRA_STATS_SUBSCRIPTION_ID =
"android.app.extra.STATS_SUBSCRIPTION_ID";
- /** Long extra of the relevant statsd_config.proto's Subscription.rule_id. */
+ /**
+ * Long extra of the relevant statsd_config.proto's Subscription.rule_id.
+ */
public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
"android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
/**
@@ -68,28 +75,34 @@
}
/**
+ * Temporary. Will be deleted.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean addConfiguration(long configKey, byte[] config, String a, String b) {
+ return addConfiguration(configKey, config);
+ }
+
+ /**
* Clients can send a configuration and simultaneously registers the name of a broadcast
* receiver that listens for when it should request data.
*
* @param configKey An arbitrary integer that allows clients to track the configuration.
* @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
* dependencies eg, conditions and matchers).
- * @param pkg The package name to receive the broadcast.
- * @param cls The name of the class that receives the broadcast.
* @return true if successful
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
+ public boolean addConfiguration(long configKey, byte[] config) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when adding configuration");
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when adding configuration");
return false;
}
- return service.addConfiguration(configKey, config, pkg, cls);
+ return service.addConfiguration(configKey, config);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when adding configuration");
+ if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when adding configuration");
return false;
}
}
@@ -107,12 +120,12 @@
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when removing configuration");
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when removing configuration");
return false;
}
return service.removeConfiguration(configKey);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when removing configuration");
+ if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when removing configuration");
return false;
}
}
@@ -121,38 +134,34 @@
/**
* Set the PendingIntent to be used when broadcasting subscriber information to the given
* subscriberId within the given config.
- *
* <p>
* Suppose that the calling uid has added a config with key configKey, and that in this config
* it is specified that when a particular anomaly is detected, a broadcast should be sent to
* a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
* that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
* when the anomaly is detected.
- *
* <p>
* When statsd sends the broadcast, the PendingIntent will used to send an intent with
* information of
- * {@link #EXTRA_STATS_CONFIG_UID},
- * {@link #EXTRA_STATS_CONFIG_KEY},
- * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
- * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
- * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
- *
+ * {@link #EXTRA_STATS_CONFIG_UID},
+ * {@link #EXTRA_STATS_CONFIG_KEY},
+ * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
+ * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
+ * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
* <p>
* This function can only be called by the owner (uid) of the config. It must be called each
* time statsd starts. The config must have been added first (via addConfiguration()).
*
- * @param configKey The integer naming the config to which this subscriber is attached.
- * @param subscriberId ID of the subscriber, as used in the config.
+ * @param configKey The integer naming the config to which this subscriber is attached.
+ * @param subscriberId ID of the subscriber, as used in the config.
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it undoes any previous setting of this subscriberId.
* @return true if successful
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean setBroadcastSubscriber(long configKey,
- long subscriberId,
- PendingIntent pendingIntent) {
+ public boolean setBroadcastSubscriber(
+ long configKey, long subscriberId, PendingIntent pendingIntent) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
@@ -175,6 +184,44 @@
}
/**
+ * Registers the operation that is called to retrieve the metrics data. This must be called
+ * each time statsd starts. The config must have been added first (via addConfiguration(),
+ * although addConfiguration could have been called on a previous boot). This operation allows
+ * statsd to send metrics data whenever statsd determines that the metrics in memory are
+ * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the
+ * data, which also deletes the retrieved metrics from statsd's memory.
+ *
+ * @param configKey The integer naming the config to which this operation is attached.
+ * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+ * associated with the given subscriberId. May be null, in which case
+ * it removes any associated pending intent with this configKey.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when registering data listener.");
+ return false;
+ }
+ if (pendingIntent == null) {
+ return service.removeDataFetchOperation(configKey);
+ } else {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ return service.setDataFetchOperation(configKey, intentSender);
+ }
+
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when registering data listener.");
+ return false;
+ }
+ }
+ }
+
+ /**
* Clients can request data with a binder call. This getter is destructive and also clears
* the retrieved metrics from statsd memory.
*
@@ -187,12 +234,12 @@
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting data");
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when getting data");
return null;
}
return service.getData(configKey);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting data");
+ if (DEBUG) Slog.d(TAG, "Failed to connecto statsd when getting data");
return null;
}
}
@@ -211,12 +258,12 @@
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting metadata");
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when getting metadata");
return null;
}
return service.getMetadata();
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting metadata");
+ if (DEBUG) Slog.d(TAG, "Failed to connecto statsd when getting metadata");
return null;
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 87f32b2..aa52cde 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -38,7 +38,6 @@
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
-import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
@@ -90,7 +89,6 @@
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
-import android.net.wifi.IRttManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.IWifiScanner;
import android.net.wifi.RttManager;
@@ -117,7 +115,6 @@
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RecoverySystem;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -639,13 +636,13 @@
registerService(Context.WIFI_RTT_SERVICE, RttManager.class,
new CachedServiceFetcher<RttManager>() {
- @Override
- public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_SERVICE);
- IRttManager service = IRttManager.Stub.asInterface(b);
- return new RttManager(ctx.getOuterContext(), service,
- ConnectivityThread.getInstanceLooper());
- }});
+ @Override
+ public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_RANGING_SERVICE);
+ IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+ return new RttManager(ctx.getOuterContext(),
+ new WifiRttManager(ctx.getOuterContext(), service));
+ }});
registerService(Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class,
new CachedServiceFetcher<WifiRttManager>() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8d16100..77e118c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9404,41 +9404,6 @@
}
/**
- * Allows/disallows printing.
- *
- * Called by a device owner or a profile owner.
- * Device owner changes policy for all users. Profile owner can override it if present.
- * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored.
- *
- * @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param enabled whether printing should be allowed or not.
- * @throws SecurityException if {@code admin} is neither device, nor profile owner.
- */
- public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) {
- try {
- mService.setPrintingEnabled(admin, enabled);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * Returns whether printing is enabled for this user.
- *
- * Always {@code false} if {@code FEATURE_PRINTING} is absent.
- * Otherwise, {@code true} by default.
- *
- * @return {@code true} iff printing is enabled.
- */
- public boolean isPrintingEnabled() {
- try {
- return mService.isPrintingEnabled();
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Called by device owner to add an override APN.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ef99007..5218a73 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -402,9 +402,6 @@
CharSequence getStartUserSessionMessage(in ComponentName admin);
CharSequence getEndUserSessionMessage(in ComponentName admin);
- void setPrintingEnabled(in ComponentName admin, boolean enabled);
- boolean isPrintingEnabled();
-
List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames);
List<String> getMeteredDataDisabled(in ComponentName admin);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 08effd9..202b894 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -77,6 +77,7 @@
TAG_KEY_DESTRUCTION,
TAG_CERT_AUTHORITY_INSTALLED,
TAG_CERT_AUTHORITY_REMOVED,
+ TAG_CRYPTO_SELF_TEST_COMPLETED,
})
public @interface SecurityLogTag {}
@@ -400,6 +401,14 @@
SecurityLogTags.SECURITY_USER_RESTRICTION_REMOVED;
/**
+ * Indicates that cryptographic functionality self test has completed. The log entry contains an
+ * {@code Integer} payload, indicating the result of the test (0 if the test failed, 1 if
+ * succeeded) and accessible via {@link SecurityEvent#getData()}.
+ */
+ public static final int TAG_CRYPTO_SELF_TEST_COMPLETED =
+ SecurityLogTags.SECURITY_CRYPTO_SELF_TEST_COMPLETED;
+
+ /**
* Event severity level indicating that the event corresponds to normal workflow.
*/
public static final int LEVEL_INFO = 1;
@@ -529,6 +538,7 @@
case TAG_USER_RESTRICTION_REMOVED:
return LEVEL_INFO;
case TAG_CERT_AUTHORITY_REMOVED:
+ case TAG_CRYPTO_SELF_TEST_COMPLETED:
return getSuccess() ? LEVEL_INFO : LEVEL_ERROR;
case TAG_CERT_AUTHORITY_INSTALLED:
case TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT:
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index be62678..b64b7e3 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -34,4 +34,5 @@
210027 security_user_restriction_added (package|3),(admin_user|1),(restriction|3)
210028 security_user_restriction_removed (package|3),(admin_user|1),(restriction|3)
210029 security_cert_authority_installed (success|1),(subject|3)
-210030 security_cert_authority_removed (success|1),(subject|3)
\ No newline at end of file
+210030 security_cert_authority_removed (success|1),(subject|3)
+210031 security_crypto_self_test_completed (success|1)
\ No newline at end of file
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
new file mode 100644
index 0000000..d8a7463
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.ActivityThread.DEBUG_ORDER;
+
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.app.ResultInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Activity relaunch callback.
+ * @hide
+ */
+public class ActivityRelaunchItem extends ClientTransactionItem {
+
+ private static final String TAG = "ActivityRelaunchItem";
+
+ private List<ResultInfo> mPendingResults;
+ private List<ReferrerIntent> mPendingNewIntents;
+ private int mConfigChanges;
+ private MergedConfiguration mConfig;
+ private boolean mPreserveWindow;
+
+ /**
+ * A record that was properly configured for relaunch. Execution will be cancelled if not
+ * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
+ */
+ private ActivityThread.ActivityClientRecord mActivityClientRecord;
+
+ @Override
+ public void preExecute(ClientTransactionHandler client, IBinder token) {
+ mActivityClientRecord = client.prepareRelaunchActivity(token, mPendingResults,
+ mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ if (mActivityClientRecord == null) {
+ if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
+ return;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
+ client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public void postExecute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ client.reportRelaunch(token, pendingActions);
+ }
+
+ // ObjectPoolItem implementation
+
+ private ActivityRelaunchItem() {}
+
+ /** Obtain an instance initialized with provided params. */
+ public static ActivityRelaunchItem obtain(List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, int configChanges, MergedConfiguration config,
+ boolean preserveWindow) {
+ ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class);
+ if (instance == null) {
+ instance = new ActivityRelaunchItem();
+ }
+ instance.mPendingResults = pendingResults;
+ instance.mPendingNewIntents = pendingNewIntents;
+ instance.mConfigChanges = configChanges;
+ instance.mConfig = config;
+ instance.mPreserveWindow = preserveWindow;
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ mPendingResults = null;
+ mPendingNewIntents = null;
+ mConfigChanges = 0;
+ mConfig = null;
+ mPreserveWindow = false;
+ mActivityClientRecord = null;
+ ObjectPool.recycle(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mPendingResults, flags);
+ dest.writeTypedList(mPendingNewIntents, flags);
+ dest.writeInt(mConfigChanges);
+ dest.writeTypedObject(mConfig, flags);
+ dest.writeBoolean(mPreserveWindow);
+ }
+
+ /** Read from Parcel. */
+ private ActivityRelaunchItem(Parcel in) {
+ mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
+ mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ mConfigChanges = in.readInt();
+ mConfig = in.readTypedObject(MergedConfiguration.CREATOR);
+ mPreserveWindow = in.readBoolean();
+ }
+
+ public static final Creator<ActivityRelaunchItem> CREATOR =
+ new Creator<ActivityRelaunchItem>() {
+ public ActivityRelaunchItem createFromParcel(Parcel in) {
+ return new ActivityRelaunchItem(in);
+ }
+
+ public ActivityRelaunchItem[] newArray(int size) {
+ return new ActivityRelaunchItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityRelaunchItem other = (ActivityRelaunchItem) o;
+ return Objects.equals(mPendingResults, other.mPendingResults)
+ && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
+ && mConfigChanges == other.mConfigChanges && Objects.equals(mConfig, other.mConfig)
+ && mPreserveWindow == other.mPreserveWindow;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mPendingResults);
+ result = 31 * result + Objects.hashCode(mPendingNewIntents);
+ result = 31 * result + mConfigChanges;
+ result = 31 * result + Objects.hashCode(mConfig);
+ result = 31 * result + (mPreserveWindow ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ActivityRelaunchItem{pendingResults=" + mPendingResults
+ + ",pendingNewIntents=" + mPendingNewIntents + ",configChanges=" + mConfigChanges
+ + ",config=" + mConfig + ",preserveWindow" + mPreserveWindow + "}";
+ }
+}
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index cbcf6c7..48a79f7 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -37,7 +37,7 @@
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
client.handleDestroyActivity(token, mFinished, mConfigChanges,
- false /* getNonConfigInstance */);
+ false /* getNonConfigInstance */, getDescription());
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java
index 073d28c..af7b7a2 100644
--- a/core/java/android/app/servertransaction/PendingTransactionActions.java
+++ b/core/java/android/app/servertransaction/PendingTransactionActions.java
@@ -44,6 +44,7 @@
private boolean mCallOnPostCreate;
private Bundle mOldState;
private StopInfo mStopInfo;
+ private boolean mReportRelaunchToWM;
public PendingTransactionActions() {
clear();
@@ -91,6 +92,24 @@
mStopInfo = stopInfo;
}
+ /**
+ * Check if we should report an activity relaunch to WindowManager. We report back for every
+ * relaunch request to ActivityManager, but only for those that were actually finished to we
+ * report to WindowManager.
+ */
+ public boolean shouldReportRelaunchToWindowManager() {
+ return mReportRelaunchToWM;
+ }
+
+ /**
+ * Set if we should report an activity relaunch to WindowManager. We report back for every
+ * relaunch request to ActivityManager, but only for those that were actually finished we report
+ * to WindowManager.
+ */
+ public void setReportRelaunchToWindowManager(boolean reportToWm) {
+ mReportRelaunchToWM = reportToWm;
+ }
+
/** Reports to server about activity stop. */
public static class StopInfo implements Runnable {
private static final String TAG = "ActivityStopInfo";
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 78b393a..840fef8 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -194,7 +194,9 @@
break;
case ON_DESTROY:
mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
- 0 /* configChanges */, false /* getNonConfigInstance */);
+ 0 /* configChanges */, false /* getNonConfigInstance */,
+ "performLifecycleSequence. cycling to:"
+ + mLifecycleSequence.get(size - 1));
break;
case ON_RESTART:
mTransactionHandler.performRestartActivity(r.token, false /* start */);
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index edb992b..6b573e9 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
package android.app.usage;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -104,12 +105,14 @@
* An event type denoting that a notification was viewed by the user.
* @hide
*/
+ @SystemApi
public static final int NOTIFICATION_SEEN = 10;
/**
* An event type denoting a change in App Standby Bucket.
* @hide
*/
+ @SystemApi
public static final int STANDBY_BUCKET_CHANGED = 11;
/** @hide */
@@ -257,6 +260,17 @@
return mShortcutId;
}
+ /**
+ * Returns the standby bucket of the app, if the event is of type
+ * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0.
+ * @return the standby bucket associated with the event.
+ * @hide
+ */
+ @SystemApi
+ public int getStandbyBucket() {
+ return mBucket;
+ }
+
/** @hide */
public Event getObfuscatedIfInstantApp() {
if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) {
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 09f9684..09a5b59 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -676,6 +676,35 @@
mExcludeSdp = excludeSdp;
}
+ /**
+ * Set the LE Transmit Data Length to be the maximum that the BT Controller is capable of. This
+ * parameter is used by the BT Controller to set the maximum transmission packet size on this
+ * connection. This function is currently used for testing only.
+ * @hide
+ */
+ public void requestMaximumTxDataLength() throws IOException {
+ if (mDevice == null) {
+ throw new IOException("requestMaximumTxDataLength is called on null device");
+ }
+
+ try {
+ if (mSocketState == SocketState.CLOSED) {
+ throw new IOException("socket closed");
+ }
+ IBluetooth bluetoothProxy =
+ BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+ if (bluetoothProxy == null) {
+ throw new IOException("Bluetooth is off");
+ }
+
+ if (DBG) Log.d(TAG, "requestMaximumTxDataLength");
+ bluetoothProxy.getSocketManager().requestMaximumTxDataLength(mDevice);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ throw new IOException("unable to send RPC: " + e.getMessage());
+ }
+ }
+
private String convertAddr(final byte[] addr) {
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b85a319..15f3777 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -560,6 +560,7 @@
*
* @param resId Resource id for the CharSequence text
*/
+ @NonNull
public final CharSequence getText(@StringRes int resId) {
return getResources().getText(resId);
}
@@ -616,12 +617,11 @@
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
- * @return An object that can be used to draw this resource, or
- * {@code null} if the resource could not be resolved.
+ * @return An object that can be used to draw this resource.
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
- @Nullable
+ @NonNull
public final Drawable getDrawable(@DrawableRes int id) {
return getResources().getDrawable(id, getTheme());
}
@@ -633,12 +633,11 @@
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
- * @return A color state list, or {@code null} if the resource could not be
- * resolved.
+ * @return A color state list.
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
- @Nullable
+ @NonNull
public final ColorStateList getColorStateList(@ColorRes int id) {
return getResources().getColorStateList(id, getTheme());
}
@@ -3539,6 +3538,7 @@
* @hide
*/
@SystemApi
+ @Deprecated
public static final String WIFI_RTT_SERVICE = "rttmanager";
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9b62f19..12d4079 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2399,6 +2399,26 @@
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
+
+ /**
+ * Broadcast Action: The current device {@link android.content.res.Configuration} has changed
+ * such that the device may be eligible for the installation of additional configuration splits.
+ * Configuration properties that can trigger this broadcast include locale and display density.
+ *
+ * <p class="note">
+ * Unlike {@link #ACTION_CONFIGURATION_CHANGED}, you <em>can</em> receive this through
+ * components declared in manifests. However, the receiver <em>must</em> hold the
+ * {@link android.Manifest.permission#INSTALL_PACKAGES} permission.
+ *
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SPLIT_CONFIGURATION_CHANGED =
+ "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
/**
* Broadcast Action: The current device's locale has changed.
*
@@ -10089,9 +10109,8 @@
}
/** @hide */
- public boolean isBrowsableWebIntent() {
+ public boolean isWebIntent() {
return ACTION_VIEW.equals(mAction)
- && hasCategory(CATEGORY_BROWSABLE)
&& hasWebURI();
}
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 1a207ba..8464e26 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -16,10 +16,14 @@
package android.content.om;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Immutable overlay information about a package. All PackageInfos that
* represent an overlay package will have a corresponding OverlayInfo.
@@ -27,6 +31,19 @@
* @hide
*/
public final class OverlayInfo implements Parcelable {
+
+ @IntDef(prefix = "STATE_", value = {
+ STATE_UNKNOWN,
+ STATE_MISSING_TARGET,
+ STATE_NO_IDMAP,
+ STATE_DISABLED,
+ STATE_ENABLED,
+ STATE_TARGET_UPGRADING,
+ STATE_OVERLAY_UPGRADING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
/**
* An internal state used as the initial state of an overlay. OverlayInfo
* objects exposed outside the {@link
@@ -61,6 +78,18 @@
public static final int STATE_ENABLED = 3;
/**
+ * The target package is currently being upgraded; the state will change
+ * once the package installation has finished.
+ */
+ public static final int STATE_TARGET_UPGRADING = 4;
+
+ /**
+ * The overlay package is currently being upgraded; the state will change
+ * once the package installation has finished.
+ */
+ public static final int STATE_OVERLAY_UPGRADING = 5;
+
+ /**
* Package name of the overlay package
*/
public final String packageName;
@@ -77,13 +106,8 @@
/**
* The state of this OverlayInfo as defined by the STATE_* constants in this class.
- *
- * @see #STATE_MISSING_TARGET
- * @see #STATE_NO_IDMAP
- * @see #STATE_DISABLED
- * @see #STATE_ENABLED
*/
- public final int state;
+ public final @State int state;
/**
* User handle for which this overlay applies
@@ -96,13 +120,13 @@
* @param source the source OverlayInfo to base the new instance on
* @param state the new state for the source OverlayInfo
*/
- public OverlayInfo(@NonNull OverlayInfo source, int state) {
+ public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
this(source.packageName, source.targetPackageName, source.baseCodePath, state,
source.userId);
}
public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
- @NonNull String baseCodePath, int state, int userId) {
+ @NonNull String baseCodePath, @State int state, int userId) {
this.packageName = packageName;
this.targetPackageName = targetPackageName;
this.baseCodePath = baseCodePath;
@@ -136,6 +160,8 @@
case STATE_NO_IDMAP:
case STATE_DISABLED:
case STATE_ENABLED:
+ case STATE_TARGET_UPGRADING:
+ case STATE_OVERLAY_UPGRADING:
break;
default:
throw new IllegalArgumentException("State " + state + " is not a valid state");
@@ -156,7 +182,8 @@
dest.writeInt(userId);
}
- public static final Parcelable.Creator<OverlayInfo> CREATOR = new Parcelable.Creator<OverlayInfo>() {
+ public static final Parcelable.Creator<OverlayInfo> CREATOR =
+ new Parcelable.Creator<OverlayInfo>() {
@Override
public OverlayInfo createFromParcel(Parcel source) {
return new OverlayInfo(source);
@@ -189,14 +216,9 @@
* Translate a state to a human readable string. Only intended for
* debugging purposes.
*
- * @see #STATE_MISSING_TARGET
- * @see #STATE_NO_IDMAP
- * @see #STATE_DISABLED
- * @see #STATE_ENABLED
- *
* @return a human readable String representing the state.
*/
- public static String stateToString(int state) {
+ public static String stateToString(@State int state) {
switch (state) {
case STATE_UNKNOWN:
return "STATE_UNKNOWN";
@@ -208,6 +230,10 @@
return "STATE_DISABLED";
case STATE_ENABLED:
return "STATE_ENABLED";
+ case STATE_TARGET_UPGRADING:
+ return "STATE_TARGET_UPGRADING";
+ case STATE_OVERLAY_UPGRADING:
+ return "STATE_OVERLAY_UPGRADING";
default:
return "<unknown state>";
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b61a6d9..1adc9f5 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1601,7 +1601,7 @@
* @hide
*/
public boolean isAllowedToUseHiddenApi() {
- return false;
+ return isSystemApp() || isUpdatedSystemApp();
}
/**
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 38f0225..361d4e4 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -18,12 +18,14 @@
import android.content.Intent;
import android.os.Bundle;
+import android.text.TextUtils;
/**
* Information needed to make an instant application resolution request.
* @hide
*/
public final class InstantAppRequest {
+
/** Response from the first phase of instant application resolution */
public final AuxiliaryResolveInfo responseObj;
/** The original intent that triggered instant application resolution */
@@ -40,6 +42,8 @@
public final Bundle verificationBundle;
/** Whether resolution occurs because an application is starting */
public final boolean resolveForStart;
+ /** The instant app digest for this request */
+ public final InstantAppResolveInfo.InstantAppDigest digest;
public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
String resolvedType, String callingPackage, int userId, Bundle verificationBundle,
@@ -51,5 +55,11 @@
this.userId = userId;
this.verificationBundle = verificationBundle;
this.resolveForStart = resolveForStart;
+ if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
+ digest = new InstantAppResolveInfo.InstantAppDigest(
+ origIntent.getData().getHost(), 5 /*maxDigests*/);
+ } else {
+ digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
+ }
}
}
diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java
index 112c5da..3a95a5f 100644
--- a/core/java/android/content/pm/InstantAppResolveInfo.java
+++ b/core/java/android/content/pm/InstantAppResolveInfo.java
@@ -26,10 +26,13 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Random;
/**
* Describes an externally resolvable instant application. There are three states that this class
@@ -227,14 +230,25 @@
*/
@SystemApi
public static final class InstantAppDigest implements Parcelable {
- private static final int DIGEST_MASK = 0xfffff000;
-
+ static final int DIGEST_MASK = 0xfffff000;
public static final InstantAppDigest UNDEFINED =
new InstantAppDigest(new byte[][]{}, new int[]{});
+
+ private static Random sRandom = null;
+ static {
+ try {
+ sRandom = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ // oh well
+ sRandom = new Random();
+ }
+ }
/** Full digest of the domain hashes */
private final byte[][] mDigestBytes;
- /** The first 4 bytes of the domain hashes */
+ /** The first 5 bytes of the domain hashes */
private final int[] mDigestPrefix;
+ /** The first 5 bytes of the domain hashes interspersed with random data */
+ private int[] mDigestPrefixSecure;
public InstantAppDigest(@NonNull String hostName) {
this(hostName, -1 /*maxDigests*/);
@@ -306,6 +320,7 @@
}
}
mDigestPrefix = in.createIntArray();
+ mDigestPrefixSecure = in.createIntArray();
}
public byte[][] getDigestBytes() {
@@ -316,6 +331,26 @@
return mDigestPrefix;
}
+ /**
+ * Returns a digest prefix with additional random prefixes interspersed.
+ * @hide
+ */
+ public int[] getDigestPrefixSecure() {
+ if (this == InstantAppResolveInfo.InstantAppDigest.UNDEFINED) {
+ return getDigestPrefix();
+ } else if (mDigestPrefixSecure == null) {
+ // let's generate some random data to intersperse throughout the set of prefixes
+ final int realSize = getDigestPrefix().length;
+ final int manufacturedSize = realSize + 10 + sRandom.nextInt(10);
+ mDigestPrefixSecure = Arrays.copyOf(getDigestPrefix(), manufacturedSize);
+ for (int i = realSize; i < manufacturedSize; i++) {
+ mDigestPrefixSecure[i] = sRandom.nextInt() & DIGEST_MASK;
+ }
+ Arrays.sort(mDigestPrefixSecure);
+ }
+ return mDigestPrefixSecure;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -323,6 +358,11 @@
@Override
public void writeToParcel(Parcel out, int flags) {
+ final boolean isUndefined = this == UNDEFINED;
+ out.writeBoolean(isUndefined);
+ if (isUndefined) {
+ return;
+ }
if (mDigestBytes == null) {
out.writeInt(-1);
} else {
@@ -332,6 +372,7 @@
}
}
out.writeIntArray(mDigestPrefix);
+ out.writeIntArray(mDigestPrefixSecure);
}
@SuppressWarnings("hiding")
@@ -339,6 +380,9 @@
new Parcelable.Creator<InstantAppDigest>() {
@Override
public InstantAppDigest createFromParcel(Parcel in) {
+ if (in.readBoolean() /* is undefined */) {
+ return UNDEFINED;
+ }
return new InstantAppDigest(in);
}
@Override
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 09a46b8..0342c93 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -468,6 +468,18 @@
dest.writeBoolean(mOverlayIsStatic);
dest.writeInt(compileSdkVersion);
dest.writeString(compileSdkVersionCodename);
+ writeSigningCertificateHistoryToParcel(dest, parcelableFlags);
+ }
+
+ private void writeSigningCertificateHistoryToParcel(Parcel dest, int parcelableFlags) {
+ if (signingCertificateHistory != null) {
+ dest.writeInt(signingCertificateHistory.length);
+ for (int i = 0; i < signingCertificateHistory.length; i++) {
+ dest.writeTypedArray(signingCertificateHistory[i], parcelableFlags);
+ }
+ } else {
+ dest.writeInt(-1);
+ }
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -523,6 +535,7 @@
mOverlayIsStatic = source.readBoolean();
compileSdkVersion = source.readInt();
compileSdkVersionCodename = source.readString();
+ readSigningCertificateHistoryFromParcel(source);
// The component lists were flattened with the redundant ApplicationInfo
// instances omitted. Distribute the canonical one here as appropriate.
@@ -534,6 +547,16 @@
}
}
+ private void readSigningCertificateHistoryFromParcel(Parcel source) {
+ int len = source.readInt();
+ if (len != -1) {
+ signingCertificateHistory = new Signature[len][];
+ for (int i = 0; i < len; i++) {
+ signingCertificateHistory[i] = source.createTypedArray(Signature.CREATOR);
+ }
+ }
+ }
+
private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) {
if (components != null) {
for (ComponentInfo ci : components) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4e5f835..486c86c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -441,6 +441,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final int MATCH_FACTORY_ONLY = 0x00200000;
/**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2da8937..377942a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -54,6 +54,7 @@
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -196,10 +197,6 @@
private static final String TAG_RESTRICT_UPDATE = "restrict-update";
private static final String TAG_USES_SPLIT = "uses-split";
- // [b/36551762] STOPSHIP remove the ability to expose components via meta-data
- // Temporary workaround; allow meta-data to expose components to instant apps
- private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed";
-
private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
/**
@@ -1290,7 +1287,6 @@
*/
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
- final AssetManager assets = newConfiguredAssetManager();
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
if (!lite.coreApp) {
@@ -1299,8 +1295,9 @@
}
}
+ final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
- final Package pkg = parseBaseApk(apkFile, assets, flags);
+ final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
pkg.setCodePath(apkFile.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
@@ -1308,28 +1305,10 @@
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
- IoUtils.closeQuietly(assets);
+ IoUtils.closeQuietly(assetLoader);
}
}
- private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
- throws PackageParserException {
- if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
- "Invalid package file: " + apkPath);
- }
-
- // The AssetManager guarantees uniqueness for asset paths, so if this asset path
- // already exists in the AssetManager, addAssetPath will only return the cookie
- // assigned to it.
- int cookie = assets.addAssetPath(apkPath);
- if (cookie == 0) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Failed adding asset path: " + apkPath);
- }
- return cookie;
- }
-
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
@@ -1345,13 +1324,15 @@
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
- final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
-
- Resources res = null;
XmlResourceParser parser = null;
try {
- res = new Resources(assets, mMetrics, null);
+ final int cookie = assets.findCookieForPath(apkPath);
+ if (cookie == 0) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+ final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
@@ -1386,15 +1367,18 @@
if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
- final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
-
final Resources res;
XmlResourceParser parser = null;
try {
- res = new Resources(assets, mMetrics, null);
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ // This must always succeed, as the path has been added to the AssetManager before.
+ final int cookie = assets.findCookieForPath(apkPath);
+ if (cookie == 0) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
+
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+ res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
@@ -1596,21 +1580,19 @@
int flags) throws PackageParserException {
final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
- AssetManager assets = null;
XmlResourceParser parser = null;
try {
- assets = newConfiguredAssetManager();
- int cookie = fd != null
- ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
- if (cookie == 0) {
+ final ApkAssets apkAssets;
+ try {
+ apkAssets = fd != null
+ ? ApkAssets.loadFromFd(fd, debugPathName, false, false)
+ : ApkAssets.loadFromPath(apkPath);
+ } catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse " + apkPath);
}
- final DisplayMetrics metrics = new DisplayMetrics();
- metrics.setToDefaults();
-
- parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+ parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
final SigningDetails signingDetails;
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
@@ -1637,7 +1619,7 @@
"Failed to parse " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
- IoUtils.closeQuietly(assets);
+ // TODO(b/72056911): Implement and call close() on ApkAssets.
}
}
@@ -4430,24 +4412,6 @@
outError)) == null) {
return null;
}
- // we don't have an attribute [or it's false], but, we have meta-data
- if (!visibleToEphemeral && a.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
- visibleToEphemeral = true; // set in case there are more intent filters
- a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
- a.info.flags &= ~ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
- owner.visibleToInstantApps = true;
- // cycle through any filters already seen
- for (int i = a.intents.size() - 1; i >= 0; --i) {
- a.intents.get(i)
- .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
- }
- if (owner.preferredActivityFilters != null) {
- for (int i = owner.preferredActivityFilters.size() - 1; i >= 0; --i) {
- owner.preferredActivityFilters.get(i)
- .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
- }
- }
- }
} else if (!receiver && parser.getName().equals("layout")) {
parseLayout(res, parser, a);
} else {
@@ -4942,7 +4906,7 @@
p.info.authority = cpname.intern();
if (!parseProviderTags(
- res, parser, visibleToEphemeral, owner, p, outError)) {
+ res, parser, visibleToEphemeral, p, outError)) {
return null;
}
@@ -4950,7 +4914,7 @@
}
private boolean parseProviderTags(Resources res, XmlResourceParser parser,
- boolean visibleToEphemeral, Package owner, Provider outInfo, String[] outError)
+ boolean visibleToEphemeral, Provider outInfo, String[] outError)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -4978,17 +4942,6 @@
outInfo.metaData, outError)) == null) {
return false;
}
- // we don't have an attribute [or it's false], but, we have meta-data
- if (!visibleToEphemeral && outInfo.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
- visibleToEphemeral = true; // set in case there are more intent filters
- outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP;
- owner.visibleToInstantApps = true;
- // cycle through any filters already seen
- for (int i = outInfo.intents.size() - 1; i >= 0; --i) {
- outInfo.intents.get(i)
- .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
- }
- }
} else if (parser.getName().equals("grant-uri-permission")) {
TypedArray sa = res.obtainAttributes(parser,
@@ -5277,17 +5230,6 @@
outError)) == null) {
return null;
}
- // we don't have an attribute [or it's false], but, we have meta-data
- if (!visibleToEphemeral && s.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
- visibleToEphemeral = true; // set in case there are more intent filters
- s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP;
- owner.visibleToInstantApps = true;
- // cycle through any filters already seen
- for (int i = s.intents.size() - 1; i >= 0; --i) {
- s.intents.get(i)
- .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
- }
- }
} else {
if (!RIGID_PARSER) {
Slog.w(TAG, "Unknown element under <service>: "
@@ -6738,31 +6680,6 @@
+ " " + packageName + "}";
}
- public String dumpState_temp() {
- String flags = "";
- flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
- flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
- if ("".equals(flags)) {
- flags = "-";
- }
- String privFlags = "";
- privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
- privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
- privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
- if ("".equals(privFlags)) {
- privFlags = "-";
- }
- return "Package{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + packageName
- + ", ver:" + getLongVersionCode()
- + ", path: " + codePath
- + ", flags: " + flags
- + ", privFlags: " + privFlags
- + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}")
- + "}";
- }
-
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/content/pm/VerifierDeviceIdentity.java b/core/java/android/content/pm/VerifierDeviceIdentity.java
index a8cdb6a..90be6f31 100644
--- a/core/java/android/content/pm/VerifierDeviceIdentity.java
+++ b/core/java/android/content/pm/VerifierDeviceIdentity.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Random;
@@ -86,6 +88,7 @@
* @return verifier device identity based on the input from the provided
* random number generator
*/
+ @VisibleForTesting
static VerifierDeviceIdentity generate(Random rng) {
long identity = rng.nextLong();
return new VerifierDeviceIdentity(identity);
diff --git a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
index 02d0a6d..79bc9a3 100644
--- a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
+++ b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
@@ -274,6 +274,7 @@
}
}
+ @GuardedBy("mLock")
private void scheduleNextMessageIfNeededLocked() {
if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) {
Message nextMessage = mPendingWork.remove(0);
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index 99eb470..9e3a8f4 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -15,10 +15,13 @@
*/
package android.content.pm.split;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;
@@ -26,6 +29,8 @@
import libcore.io.IoUtils;
+import java.io.IOException;
+
/**
* Loads the base and split APKs into a single AssetManager.
* @hide
@@ -33,68 +38,66 @@
public class DefaultSplitAssetLoader implements SplitAssetLoader {
private final String mBaseCodePath;
private final String[] mSplitCodePaths;
- private final int mFlags;
-
+ private final @ParseFlags int mFlags;
private AssetManager mCachedAssetManager;
- public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
+ public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) {
mBaseCodePath = pkg.baseCodePath;
mSplitCodePaths = pkg.splitCodePaths;
mFlags = flags;
}
- private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
- throws PackageParser.PackageParserException {
- if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
- throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
- "Invalid package file: " + apkPath);
+ private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+ throws PackageParserException {
+ if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + path);
}
- if (assets.addAssetPath(apkPath) == 0) {
- throw new PackageParser.PackageParserException(
- INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Failed adding asset path: " + apkPath);
+ try {
+ return ApkAssets.loadFromPath(path);
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_FAILED_INVALID_APK,
+ "Failed to load APK at path " + path, e);
}
}
@Override
- public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+ public AssetManager getBaseAssetManager() throws PackageParserException {
if (mCachedAssetManager != null) {
return mCachedAssetManager;
}
- AssetManager assets = new AssetManager();
- try {
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
- loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
+ ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
+ ? mSplitCodePaths.length : 0) + 1];
- if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
- for (String apkPath : mSplitCodePaths) {
- loadApkIntoAssetManager(assets, apkPath, mFlags);
- }
- }
+ // Load the base.
+ int splitIdx = 0;
+ apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
- mCachedAssetManager = assets;
- assets = null;
- return mCachedAssetManager;
- } finally {
- if (assets != null) {
- IoUtils.closeQuietly(assets);
+ // Load any splits.
+ if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+ for (String apkPath : mSplitCodePaths) {
+ apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
}
}
+
+ AssetManager assets = new AssetManager();
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+
+ mCachedAssetManager = assets;
+ return mCachedAssetManager;
}
@Override
- public AssetManager getSplitAssetManager(int splitIdx)
- throws PackageParser.PackageParserException {
+ public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
return getBaseAssetManager();
}
@Override
public void close() throws Exception {
- if (mCachedAssetManager != null) {
- IoUtils.closeQuietly(mCachedAssetManager);
- }
+ IoUtils.closeQuietly(mCachedAssetManager);
}
}
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 16023f0..58eaabf 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -15,17 +15,21 @@
*/
package android.content.pm.split;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
import android.annotation.NonNull;
+import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.SparseArray;
import libcore.io.IoUtils;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -34,17 +38,15 @@
* is to be used when an application opts-in to isolated split loading.
* @hide
*/
-public class SplitAssetDependencyLoader
- extends SplitDependencyLoader<PackageParser.PackageParserException>
+public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException>
implements SplitAssetLoader {
private final String[] mSplitPaths;
- private final int mFlags;
-
- private String[][] mCachedPaths;
- private AssetManager[] mCachedAssetManagers;
+ private final @ParseFlags int mFlags;
+ private final ApkAssets[][] mCachedSplitApks;
+ private final AssetManager[] mCachedAssetManagers;
public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
- SparseArray<int[]> dependencies, int flags) {
+ SparseArray<int[]> dependencies, @ParseFlags int flags) {
super(dependencies);
// The base is inserted into index 0, so we need to shift all the splits by 1.
@@ -53,7 +55,7 @@
System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
mFlags = flags;
- mCachedPaths = new String[mSplitPaths.length][];
+ mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
mCachedAssetManagers = new AssetManager[mSplitPaths.length];
}
@@ -62,58 +64,60 @@
return mCachedAssetManagers[splitIdx] != null;
}
- private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
- throws PackageParser.PackageParserException {
- final AssetManager assets = new AssetManager();
- try {
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
-
- for (String assetPath : assetPaths) {
- if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
- !PackageParser.isApkPath(assetPath)) {
- throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
- "Invalid package file: " + assetPath);
- }
-
- if (assets.addAssetPath(assetPath) == 0) {
- throw new PackageParser.PackageParserException(
- INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Failed adding asset path: " + assetPath);
- }
- }
- return assets;
- } catch (Throwable e) {
- IoUtils.closeQuietly(assets);
- throw e;
+ private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+ throws PackageParserException {
+ if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + path);
}
+
+ try {
+ return ApkAssets.loadFromPath(path);
+ } catch (IOException e) {
+ throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK,
+ "Failed to load APK at path " + path, e);
+ }
+ }
+
+ private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
+ final AssetManager assets = new AssetManager();
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+ return assets;
}
@Override
protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
- int parentSplitIdx) throws PackageParser.PackageParserException {
- final ArrayList<String> assetPaths = new ArrayList<>();
+ int parentSplitIdx) throws PackageParserException {
+ final ArrayList<ApkAssets> assets = new ArrayList<>();
+
+ // Include parent ApkAssets.
if (parentSplitIdx >= 0) {
- Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
+ Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
}
- assetPaths.add(mSplitPaths[splitIdx]);
+ // Include this ApkAssets.
+ assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
+
+ // Load and include all config splits for this feature.
for (int configSplitIdx : configSplitIndices) {
- assetPaths.add(mSplitPaths[configSplitIdx]);
+ assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
}
- mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
- mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
- mFlags);
+
+ // Cache the results.
+ mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
+ mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]);
}
@Override
- public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+ public AssetManager getBaseAssetManager() throws PackageParserException {
loadDependenciesForSplit(0);
return mCachedAssetManagers[0];
}
@Override
- public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
+ public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
// Since we insert the base at position 0, and PackageParser keeps splits separate from
// the base, we need to adjust the index.
loadDependenciesForSplit(idx + 1);
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
new file mode 100644
index 0000000..fd664bc
--- /dev/null
+++ b/core/java/android/content/res/ApkAssets.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.res;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * The loaded, immutable, in-memory representation of an APK.
+ *
+ * The main implementation is native C++ and there is very little API surface exposed here. The APK
+ * is mainly accessed via {@link AssetManager}.
+ *
+ * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
+ * making the creation of AssetManagers very cheap.
+ * @hide
+ */
+public final class ApkAssets {
+ @GuardedBy("this") private final long mNativePtr;
+ @GuardedBy("this") private StringBlock mStringBlock;
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
+ throws IOException {
+ return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
+ * loaded as a shared library.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
+ boolean forceSharedLibrary) throws IOException {
+ return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable APK.
+ * @param friendlyName The friendly name used to identify this ApkAssets when logging.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
+ * loaded as a shared library.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
+ throws IOException {
+ return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
+ * is encoded within the IDMAP.
+ *
+ * @param idmapPath Path to the IDMAP of an overlay APK.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
+ throws IOException {
+ return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+ }
+
+ private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ throws IOException {
+ Preconditions.checkNotNull(path, "path");
+ mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ }
+
+ private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
+ boolean forceSharedLib) throws IOException {
+ Preconditions.checkNotNull(fd, "fd");
+ Preconditions.checkNotNull(friendlyName, "friendlyName");
+ mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ }
+
+ @NonNull String getAssetPath() {
+ synchronized (this) {
+ return nativeGetAssetPath(mNativePtr);
+ }
+ }
+
+ CharSequence getStringFromPool(int idx) {
+ synchronized (this) {
+ return mStringBlock.get(idx);
+ }
+ }
+
+ /**
+ * Retrieve a parser for a compiled XML file. This is associated with a single APK and
+ * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
+ * dynamically assigned runtime package IDs.
+ *
+ * @param fileName The path to the file within the APK.
+ * @return An XmlResourceParser.
+ * @throws IOException if the file was not found or an error occurred retrieving it.
+ */
+ public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
+ synchronized (this) {
+ long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
+ try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
+ XmlResourceParser parser = block.newParser();
+ // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
+ // which makes newParser always return non-null. But let's be paranoid.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
+ }
+ }
+
+ /**
+ * Returns false if the underlying APK was changed since this ApkAssets was loaded.
+ */
+ public boolean isUpToDate() {
+ synchronized (this) {
+ return nativeIsUpToDate(mNativePtr);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestroy(mNativePtr);
+ }
+
+ private static native long nativeLoad(
+ @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ throws IOException;
+ private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+ throws IOException;
+ private static native void nativeDestroy(long ptr);
+ private static native @NonNull String nativeGetAssetPath(long ptr);
+ private static native long nativeGetStringBlock(long ptr);
+ private static native boolean nativeIsUpToDate(long ptr);
+ private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7866560..bb90700 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -18,9 +18,11 @@
import android.annotation.AnyRes;
import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
+import android.annotation.StyleRes;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
import android.os.ParcelFileDescriptor;
@@ -28,10 +30,20 @@
import android.util.SparseArray;
import android.util.TypedValue;
-import java.io.FileDescriptor;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.channels.FileLock;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -42,7 +54,19 @@
* bytes.
*/
public final class AssetManager implements AutoCloseable {
- /* modes used when opening an asset */
+ private static final String TAG = "AssetManager";
+ private static final boolean DEBUG_REFS = false;
+
+ private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
+
+ private static final Object sSync = new Object();
+
+ private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0];
+
+ // Not private for LayoutLib's BridgeAssetManager.
+ @GuardedBy("sSync") static AssetManager sSystem = null;
+
+ @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];
/**
* Mode for {@link #open(String, int)}: no specific information about how
@@ -65,146 +89,301 @@
*/
public static final int ACCESS_BUFFER = 3;
- private static final String TAG = "AssetManager";
- private static final boolean localLOGV = false || false;
-
- private static final boolean DEBUG_REFS = false;
-
- private static final Object sSync = new Object();
- /*package*/ static AssetManager sSystem = null;
+ @GuardedBy("this") private final TypedValue mValue = new TypedValue();
+ @GuardedBy("this") private final long[] mOffsets = new long[2];
- private final TypedValue mValue = new TypedValue();
- private final long[] mOffsets = new long[2];
-
- // For communication with native code.
- private long mObject;
+ // Pointer to native implementation, stuffed inside a long.
+ @GuardedBy("this") private long mObject;
- private StringBlock mStringBlocks[] = null;
-
- private int mNumRefs = 1;
- private boolean mOpen = true;
- private HashMap<Long, RuntimeException> mRefStacks;
-
+ // The loaded asset paths.
+ @GuardedBy("this") private ApkAssets[] mApkAssets;
+
+ // Debug/reference counting implementation.
+ @GuardedBy("this") private boolean mOpen = true;
+ @GuardedBy("this") private int mNumRefs = 1;
+ @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
+
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
- * {@hide}
+ * @hide
*/
public AssetManager() {
- synchronized (this) {
- if (DEBUG_REFS) {
- mNumRefs = 0;
- incRefsLocked(this.hashCode());
- }
- init(false);
- if (localLOGV) Log.v(TAG, "New asset manager: " + this);
- ensureSystemAssets();
+ final ApkAssets[] assets;
+ synchronized (sSync) {
+ createSystemAssetsInZygoteLocked();
+ assets = sSystemApkAssets;
+ }
+
+ mObject = nativeCreate();
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(hashCode());
+ }
+
+ // Always set the framework resources.
+ setApkAssets(assets, false /*invalidateCaches*/);
+ }
+
+ /**
+ * Private constructor that doesn't call ensureSystemAssets.
+ * Used for the creation of system assets.
+ */
+ @SuppressWarnings("unused")
+ private AssetManager(boolean sentinel) {
+ mObject = nativeCreate();
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(hashCode());
}
}
- private static void ensureSystemAssets() {
- synchronized (sSync) {
- if (sSystem == null) {
- AssetManager system = new AssetManager(true);
- system.makeStringBlocks(null);
- sSystem = system;
- }
+ /**
+ * This must be called from Zygote so that system assets are shared by all applications.
+ */
+ @GuardedBy("sSync")
+ private static void createSystemAssetsInZygoteLocked() {
+ if (sSystem != null) {
+ return;
+ }
+
+ // Make sure that all IDMAPs are up to date.
+ nativeVerifySystemIdmaps();
+
+ try {
+ ArrayList<ApkAssets> apkAssets = new ArrayList<>();
+ apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
+ loadStaticRuntimeOverlays(apkAssets);
+
+ sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
+ sSystem = new AssetManager(true /*sentinel*/);
+ sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create system AssetManager", e);
}
}
-
- private AssetManager(boolean isSystem) {
- if (DEBUG_REFS) {
- synchronized (this) {
- mNumRefs = 0;
- incRefsLocked(this.hashCode());
- }
+
+ /**
+ * Loads the static runtime overlays declared in /data/resource-cache/overlays.list.
+ * Throws an exception if the file is corrupt or if loading the APKs referenced by the file
+ * fails. Returns quietly if the overlays.list file doesn't exist.
+ * @param outApkAssets The list to fill with the loaded ApkAssets.
+ */
+ private static void loadStaticRuntimeOverlays(ArrayList<ApkAssets> outApkAssets)
+ throws IOException {
+ final FileInputStream fis;
+ try {
+ fis = new FileInputStream("/data/resource-cache/overlays.list");
+ } catch (FileNotFoundException e) {
+ // We might not have any overlays, this is fine. We catch here since ApkAssets
+ // loading can also fail with the same exception, which we would want to propagate.
+ Log.i(TAG, "no overlays.list file found");
+ return;
}
- init(true);
- if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+
+ try {
+ // Acquire a lock so that any idmap scanning doesn't impact the current set.
+ // The order of this try-with-resources block matters. We must release the lock, and
+ // then close the file streams when exiting the block.
+ try (final BufferedReader br = new BufferedReader(new InputStreamReader(fis));
+ final FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE, true /*shared*/)) {
+ for (String line; (line = br.readLine()) != null; ) {
+ final String idmapPath = line.split(" ")[1];
+ outApkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+ }
+ }
+ } finally {
+ // When BufferedReader is closed above, FileInputStream is closed as well. But let's be
+ // paranoid.
+ IoUtils.closeQuietly(fis);
+ }
}
/**
* Return a global shared asset manager that provides access to only
* system assets (no application assets).
- * {@hide}
+ * @hide
*/
public static AssetManager getSystem() {
- ensureSystemAssets();
- return sSystem;
+ synchronized (sSync) {
+ createSystemAssetsInZygoteLocked();
+ return sSystem;
+ }
}
/**
* Close this asset manager.
*/
+ @Override
public void close() {
- synchronized(this) {
- //System.out.println("Release: num=" + mNumRefs
- // + ", released=" + mReleased);
+ synchronized (this) {
+ if (!mOpen) {
+ return;
+ }
+
+ mOpen = false;
+ decRefsLocked(hashCode());
+ }
+ }
+
+ /**
+ * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)}
+ * family of methods.
+ *
+ * @param apkAssets The new set of paths.
+ * @param invalidateCaches Whether to invalidate any caches. This should almost always be true.
+ * Set this to false if you are appending new resources
+ * (not new configurations).
+ * @hide
+ */
+ public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
+ Preconditions.checkNotNull(apkAssets, "apkAssets");
+
+ // Copy the apkAssets, but prepend the system assets (framework + overlays).
+ final ApkAssets[] newApkAssets = new ApkAssets[apkAssets.length + sSystemApkAssets.length];
+ System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);
+ System.arraycopy(apkAssets, 0, newApkAssets, sSystemApkAssets.length, apkAssets.length);
+
+ synchronized (this) {
+ ensureOpenLocked();
+ mApkAssets = newApkAssets;
+ nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);
+ if (invalidateCaches) {
+ // Invalidate all caches.
+ invalidateCachesLocked(-1);
+ }
+ }
+ }
+
+ /**
+ * Invalidates the caches in this AssetManager according to the bitmask `diff`.
+ *
+ * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}.
+ * @see ActivityInfo.Config
+ */
+ private void invalidateCachesLocked(int diff) {
+ // TODO(adamlesinski): Currently there are no caches to invalidate in Java code.
+ }
+
+ /**
+ * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this
+ * returns a 0-length array.
+ * @hide
+ */
+ public @NonNull ApkAssets[] getApkAssets() {
+ synchronized (this) {
if (mOpen) {
- mOpen = false;
- decRefsLocked(this.hashCode());
+ return mApkAssets;
}
}
+ return sEmptyApkAssets;
}
/**
- * Retrieves the string value associated with a particular resource
- * identifier for the current configuration.
- *
- * @param resId the resource identifier to load
- * @return the string value, or {@code null}
+ * Returns a cookie for use with the other APIs of AssetManager.
+ * @return 0 if the path was not found, otherwise a positive integer cookie representing
+ * this path in the AssetManager.
+ * @hide
*/
- @Nullable
- final CharSequence getResourceText(@StringRes int resId) {
+ public int findCookieForPath(@NonNull String path) {
+ Preconditions.checkNotNull(path, "path");
synchronized (this) {
- final TypedValue outValue = mValue;
- if (getResourceValue(resId, 0, outValue, true)) {
- return outValue.coerceToString();
+ ensureValidLocked();
+ final int count = mApkAssets.length;
+ for (int i = 0; i < count; i++) {
+ if (path.equals(mApkAssets[i].getAssetPath())) {
+ return i + 1;
+ }
}
- return null;
}
+ return 0;
}
/**
- * Retrieves the string value associated with a particular resource
- * identifier for the current configuration.
- *
- * @param resId the resource identifier to load
- * @param bagEntryId
- * @return the string value, or {@code null}
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
*/
- @Nullable
- final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+ @Deprecated
+ public int addAssetPath(String path) {
+ return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ public int addAssetPathAsSharedLibrary(String path) {
+ return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/);
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ public int addOverlayPath(String path) {
+ return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
+ }
+
+ private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
+ Preconditions.checkNotNull(path, "path");
synchronized (this) {
- final TypedValue outValue = mValue;
- final int block = loadResourceBagValue(resId, bagEntryId, outValue, true);
- if (block < 0) {
- return null;
+ ensureOpenLocked();
+ final int count = mApkAssets.length;
+ for (int i = 0; i < count; i++) {
+ if (mApkAssets[i].getAssetPath().equals(path)) {
+ return i + 1;
+ }
}
- // Convert the changing configurations flags populated by native code.
- outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
- outValue.changingConfigurations);
-
- if (outValue.type == TypedValue.TYPE_STRING) {
- return mStringBlocks[block].get(outValue.data);
+ final ApkAssets assets;
+ try {
+ if (overlay) {
+ // TODO(b/70343104): This hardcoded path will be removed once
+ // addAssetPathInternal is deleted.
+ final String idmapPath = "/data/resource-cache/"
+ + path.substring(1).replace('/', '@')
+ + "@idmap";
+ assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
+ } else {
+ assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib);
+ }
+ } catch (IOException e) {
+ return 0;
}
- return outValue.coerceToString();
+
+ final ApkAssets[] newApkAssets = Arrays.copyOf(mApkAssets, count + 1);
+ newApkAssets[count] = assets;
+ setApkAssets(newApkAssets, true);
+ return count + 1;
}
}
/**
- * Retrieves the string array associated with a particular resource
- * identifier for the current configuration.
- *
- * @param resId the resource identifier of the string array
- * @return the string array, or {@code null}
+ * Ensures that the native implementation has not been destroyed.
+ * The AssetManager may have been closed, but references to it still exist
+ * and therefore the native implementation is not destroyed.
*/
- @Nullable
- final String[] getResourceStringArray(@ArrayRes int resId) {
- return getArrayStringResource(resId);
+ @GuardedBy("this")
+ private void ensureValidLocked() {
+ if (mObject == 0) {
+ throw new RuntimeException("AssetManager has been destroyed");
+ }
+ }
+
+ /**
+ * Ensures that the AssetManager has not been explicitly closed. If this method passes,
+ * then this implies that ensureValidLocked() also passes.
+ */
+ @GuardedBy("this")
+ private void ensureOpenLocked() {
+ // If mOpen is true, this implies that mObject != 0.
+ if (!mOpen) {
+ throw new RuntimeException("AssetManager has been closed");
+ }
}
/**
@@ -219,11 +398,14 @@
* @return {@code true} if the data was loaded into {@code outValue},
* {@code false} otherwise
*/
- final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
+ boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
+ Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
- final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
- if (block < 0) {
+ ensureValidLocked();
+ final int cookie = nativeGetResourceValue(
+ mObject, resId, (short) densityDpi, outValue, resolveRefs);
+ if (cookie <= 0) {
return false;
}
@@ -232,38 +414,156 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mStringBlocks[block].get(outValue.data);
+ outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
/**
+ * Retrieves the string value associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier to load
+ * @return the string value, or {@code null}
+ */
+ @Nullable CharSequence getResourceText(@StringRes int resId) {
+ synchronized (this) {
+ final TypedValue outValue = mValue;
+ if (getResourceValue(resId, 0, outValue, true)) {
+ return outValue.coerceToString();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves the string value associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier to load
+ * @param bagEntryId the index into the bag to load
+ * @return the string value, or {@code null}
+ */
+ @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+ synchronized (this) {
+ ensureValidLocked();
+ final TypedValue outValue = mValue;
+ final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue);
+ if (cookie <= 0) {
+ return null;
+ }
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ }
+ return outValue.coerceToString();
+ }
+ }
+
+ int getResourceArraySize(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceArraySize(mObject, resId);
+ }
+ }
+
+ /**
+ * Populates `outData` with array elements of `resId`. `outData` is normally
+ * used with
+ * {@link TypedArray}.
+ *
+ * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES}
+ * long,
+ * with the indices of the data representing the type, value, asset cookie,
+ * resource ID,
+ * configuration change mask, and density of the element.
+ *
+ * @param resId The resource ID of an array resource.
+ * @param outData The array to populate with data.
+ * @return The length of the array.
+ *
+ * @see TypedArray#STYLE_TYPE
+ * @see TypedArray#STYLE_DATA
+ * @see TypedArray#STYLE_ASSET_COOKIE
+ * @see TypedArray#STYLE_RESOURCE_ID
+ * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS
+ * @see TypedArray#STYLE_DENSITY
+ */
+ int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) {
+ Preconditions.checkNotNull(outData, "outData");
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceArray(mObject, resId, outData);
+ }
+ }
+
+ /**
+ * Retrieves the string array associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier of the string array
+ * @return the string array, or {@code null}
+ */
+ @Nullable String[] getResourceStringArray(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceStringArray(mObject, resId);
+ }
+ }
+
+ /**
* Retrieve the text array associated with a particular resource
* identifier.
*
* @param resId the resource id of the string array
*/
- final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+ @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
synchronized (this) {
- final int[] rawInfoArray = getArrayStringInfo(resId);
+ ensureValidLocked();
+ final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId);
if (rawInfoArray == null) {
return null;
}
+
final int rawInfoArrayLen = rawInfoArray.length;
final int infoArrayLen = rawInfoArrayLen / 2;
- int block;
- int index;
final CharSequence[] retArray = new CharSequence[infoArrayLen];
for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
- block = rawInfoArray[i];
- index = rawInfoArray[i + 1];
- retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null;
+ int cookie = rawInfoArray[i];
+ int index = rawInfoArray[i + 1];
+ retArray[j] = (index >= 0 && cookie > 0)
+ ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
}
return retArray;
}
}
+ @Nullable int[] getResourceIntArray(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceIntArray(mObject, resId);
+ }
+ }
+
+ /**
+ * Get the attributes for a style resource. These are the <item>
+ * elements in
+ * a <style> resource.
+ * @param resId The resource ID of the style
+ * @return An array of attribute IDs.
+ */
+ @AttrRes int[] getStyleAttributes(@StyleRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetStyleAttributes(mObject, resId);
+ }
+ }
+
/**
* Populates {@code outValue} with the data associated with a particular
* resource identifier for the current configuration. Resolves theme
@@ -277,73 +577,88 @@
* @return {@code true} if the data was loaded into {@code outValue},
* {@code false} otherwise
*/
- final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
+ boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
boolean resolveRefs) {
- final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs);
- if (block < 0) {
- return false;
- }
-
- // Convert the changing configurations flags populated by native code.
- outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
- outValue.changingConfigurations);
-
- if (outValue.type == TypedValue.TYPE_STRING) {
- final StringBlock[] blocks = ensureStringBlocks();
- outValue.string = blocks[block].get(outValue.data);
- }
- return true;
- }
-
- /**
- * Ensures the string blocks are loaded.
- *
- * @return the string blocks
- */
- @NonNull
- final StringBlock[] ensureStringBlocks() {
+ Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
- if (mStringBlocks == null) {
- makeStringBlocks(sSystem.mStringBlocks);
+ ensureValidLocked();
+ final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue,
+ resolveRefs);
+ if (cookie <= 0) {
+ return false;
}
- return mStringBlocks;
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ }
+ return true;
}
}
- /*package*/ final void makeStringBlocks(StringBlock[] seed) {
- final int seedNum = (seed != null) ? seed.length : 0;
- final int num = getStringBlockCount();
- mStringBlocks = new StringBlock[num];
- if (localLOGV) Log.v(TAG, "Making string blocks for " + this
- + ": " + num);
- for (int i=0; i<num; i++) {
- if (i < seedNum) {
- mStringBlocks[i] = seed[i];
- } else {
- mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
- }
- }
- }
-
- /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
+ void dumpTheme(long theme, int priority, String tag, String prefix) {
synchronized (this) {
- // Cookies map to string blocks starting at 1.
- return mStringBlocks[cookie - 1].get(id);
+ ensureValidLocked();
+ nativeThemeDump(mObject, theme, priority, tag, prefix);
}
}
+ @Nullable String getResourceName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceName(mObject, resId);
+ }
+ }
+
+ @Nullable String getResourcePackageName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourcePackageName(mObject, resId);
+ }
+ }
+
+ @Nullable String getResourceTypeName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceTypeName(mObject, resId);
+ }
+ }
+
+ @Nullable String getResourceEntryName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceEntryName(mObject, resId);
+ }
+ }
+
+ @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType,
+ @Nullable String defPackage) {
+ synchronized (this) {
+ ensureValidLocked();
+ // name is checked in JNI.
+ return nativeGetResourceIdentifier(mObject, name, defType, defPackage);
+ }
+ }
+
+ CharSequence getPooledStringForCookie(int cookie, int id) {
+ // Cookies map to ApkAssets starting at 1.
+ return getApkAssets()[cookie - 1].getStringFromPool(id);
+ }
+
/**
* Open an asset using ACCESS_STREAMING mode. This provides access to
* files that have been bundled with an application as assets -- that is,
* files placed in to the "assets" directory.
*
- * @param fileName The name of the asset to open. This name can be
- * hierarchical.
+ * @param fileName The name of the asset to open. This name can be hierarchical.
*
* @see #open(String, int)
* @see #list
*/
- public final InputStream open(String fileName) throws IOException {
+ public @NonNull InputStream open(@NonNull String fileName) throws IOException {
return open(fileName, ACCESS_STREAMING);
}
@@ -353,8 +668,7 @@
* with an application as assets -- that is, files placed in to the
* "assets" directory.
*
- * @param fileName The name of the asset to open. This name can be
- * hierarchical.
+ * @param fileName The name of the asset to open. This name can be hierarchical.
* @param accessMode Desired access mode for retrieving the data.
*
* @see #ACCESS_UNKNOWN
@@ -364,34 +678,40 @@
* @see #open(String)
* @see #list
*/
- public final InputStream open(String fileName, int accessMode)
- throws IOException {
+ public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final long asset = nativeOpenAsset(mObject, fileName, accessMode);
+ if (asset == 0) {
+ throw new FileNotFoundException("Asset file: " + fileName);
}
- long asset = openAsset(fileName, accessMode);
- if (asset != 0) {
- AssetInputStream res = new AssetInputStream(asset);
- incRefsLocked(res.hashCode());
- return res;
- }
+ final AssetInputStream assetInputStream = new AssetInputStream(asset);
+ incRefsLocked(assetInputStream.hashCode());
+ return assetInputStream;
}
- throw new FileNotFoundException("Asset file: " + fileName);
}
- public final AssetFileDescriptor openFd(String fileName)
- throws IOException {
+ /**
+ * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}.
+ * This provides access to files that have been bundled with an application as assets -- that
+ * is, files placed in to the "assets" directory.
+ *
+ * The asset must be uncompressed, or an exception will be thrown.
+ *
+ * @param fileName The name of the asset to open. This name can be hierarchical.
+ * @return An open AssetFileDescriptor.
+ */
+ public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
+ if (pfd == null) {
+ throw new FileNotFoundException("Asset file: " + fileName);
}
- ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets);
- if (pfd != null) {
- return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
- }
+ return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
}
- throw new FileNotFoundException("Asset file: " + fileName);
}
/**
@@ -406,90 +726,121 @@
*
* @see #open
*/
- public native final String[] list(String path)
- throws IOException;
+ public @Nullable String[] list(@NonNull String path) throws IOException {
+ Preconditions.checkNotNull(path, "path");
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeList(mObject, path);
+ }
+ }
/**
- * {@hide}
* Open a non-asset file as an asset using ACCESS_STREAMING mode. This
* provides direct access to all of the files included in an application
* package (not only its assets). Applications should not normally use
* this.
- *
+ *
+ * @param fileName Name of the asset to retrieve.
+ *
* @see #open(String)
+ * @hide
*/
- public final InputStream openNonAsset(String fileName) throws IOException {
+ public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException {
return openNonAsset(0, fileName, ACCESS_STREAMING);
}
/**
- * {@hide}
* Open a non-asset file as an asset using a specific access mode. This
* provides direct access to all of the files included in an application
* package (not only its assets). Applications should not normally use
* this.
- *
+ *
+ * @param fileName Name of the asset to retrieve.
+ * @param accessMode Desired access mode for retrieving the data.
+ *
+ * @see #ACCESS_UNKNOWN
+ * @see #ACCESS_STREAMING
+ * @see #ACCESS_RANDOM
+ * @see #ACCESS_BUFFER
* @see #open(String, int)
+ * @hide
*/
- public final InputStream openNonAsset(String fileName, int accessMode)
- throws IOException {
+ public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode)
+ throws IOException {
return openNonAsset(0, fileName, accessMode);
}
/**
- * {@hide}
* Open a non-asset in a specified package. Not for use by applications.
- *
+ *
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
+ * @hide
*/
- public final InputStream openNonAsset(int cookie, String fileName)
- throws IOException {
+ public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName)
+ throws IOException {
return openNonAsset(cookie, fileName, ACCESS_STREAMING);
}
/**
- * {@hide}
* Open a non-asset in a specified package. Not for use by applications.
- *
+ *
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
* @param accessMode Desired access mode for retrieving the data.
+ * @hide
*/
- public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
- throws IOException {
+ public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
+ if (asset == 0) {
+ throw new FileNotFoundException("Asset absolute file: " + fileName);
}
- long asset = openNonAssetNative(cookie, fileName, accessMode);
- if (asset != 0) {
- AssetInputStream res = new AssetInputStream(asset);
- incRefsLocked(res.hashCode());
- return res;
- }
+ final AssetInputStream assetInputStream = new AssetInputStream(asset);
+ incRefsLocked(assetInputStream.hashCode());
+ return assetInputStream;
}
- throw new FileNotFoundException("Asset absolute file: " + fileName);
}
- public final AssetFileDescriptor openNonAssetFd(String fileName)
+ /**
+ * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
+ * This provides direct access to all of the files included in an application
+ * package (not only its assets). Applications should not normally use this.
+ *
+ * The asset must not be compressed, or an exception will be thrown.
+ *
+ * @param fileName Name of the asset to retrieve.
+ */
+ public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName)
throws IOException {
return openNonAssetFd(0, fileName);
}
-
- public final AssetFileDescriptor openNonAssetFd(int cookie,
- String fileName) throws IOException {
+
+ /**
+ * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
+ * This provides direct access to all of the files included in an application
+ * package (not only its assets). Applications should not normally use this.
+ *
+ * The asset must not be compressed, or an exception will be thrown.
+ *
+ * @param cookie Identifier of the package to be opened.
+ * @param fileName Name of the asset to retrieve.
+ */
+ public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName)
+ throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final ParcelFileDescriptor pfd =
+ nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
+ if (pfd == null) {
+ throw new FileNotFoundException("Asset absolute file: " + fileName);
}
- ParcelFileDescriptor pfd = openNonAssetFdNative(cookie,
- fileName, mOffsets);
- if (pfd != null) {
- return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
- }
+ return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
}
- throw new FileNotFoundException("Asset absolute file: " + fileName);
}
/**
@@ -497,7 +848,7 @@
*
* @param fileName The name of the file to retrieve.
*/
- public final XmlResourceParser openXmlResourceParser(String fileName)
+ public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
throws IOException {
return openXmlResourceParser(0, fileName);
}
@@ -508,270 +859,265 @@
* @param cookie Identifier of the package to be opened.
* @param fileName The name of the file to retrieve.
*/
- public final XmlResourceParser openXmlResourceParser(int cookie,
- String fileName) throws IOException {
- XmlBlock block = openXmlBlockAsset(cookie, fileName);
- XmlResourceParser rp = block.newParser();
- block.close();
- return rp;
+ public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
+ throws IOException {
+ try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
+ XmlResourceParser parser = block.newParser();
+ // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
+ // a valid native pointer, which makes newParser always return non-null. But let's
+ // be paranoid.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
}
/**
- * {@hide}
- * Retrieve a non-asset as a compiled XML file. Not for use by
- * applications.
+ * Retrieve a non-asset as a compiled XML file. Not for use by applications.
*
* @param fileName The name of the file to retrieve.
+ * @hide
*/
- /*package*/ final XmlBlock openXmlBlockAsset(String fileName)
- throws IOException {
+ @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
return openXmlBlockAsset(0, fileName);
}
/**
- * {@hide}
* Retrieve a non-asset as a compiled XML file. Not for use by
* applications.
*
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
+ * @hide
*/
- /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
- throws IOException {
+ @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+ if (xmlBlock == 0) {
+ throw new FileNotFoundException("Asset XML file: " + fileName);
}
- long xmlBlock = openXmlAssetNative(cookie, fileName);
- if (xmlBlock != 0) {
- XmlBlock res = new XmlBlock(this, xmlBlock);
- incRefsLocked(res.hashCode());
- return res;
- }
+ final XmlBlock block = new XmlBlock(this, xmlBlock);
+ incRefsLocked(block.hashCode());
+ return block;
}
- throw new FileNotFoundException("Asset XML file: " + fileName);
}
- /*package*/ void xmlBlockGone(int id) {
+ void xmlBlockGone(int id) {
synchronized (this) {
decRefsLocked(id);
}
}
- /*package*/ final long createTheme() {
+ void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+ @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
+ long outIndicesAddress) {
+ Preconditions.checkNotNull(inAttrs, "inAttrs");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
- }
- long res = newTheme();
- incRefsLocked(res);
- return res;
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
+ parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
+ outIndicesAddress);
}
}
- /*package*/ final void releaseTheme(long theme) {
+ boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+ @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
+ @NonNull int[] outIndices) {
+ Preconditions.checkNotNull(inAttrs, "inAttrs");
+ Preconditions.checkNotNull(outValues, "outValues");
+ Preconditions.checkNotNull(outIndices, "outIndices");
synchronized (this) {
- deleteTheme(theme);
- decRefsLocked(theme);
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ return nativeResolveAttrs(mObject,
+ themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices);
}
}
+ boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs,
+ @NonNull int[] outValues, @NonNull int[] outIndices) {
+ Preconditions.checkNotNull(parser, "parser");
+ Preconditions.checkNotNull(inAttrs, "inAttrs");
+ Preconditions.checkNotNull(outValues, "outValues");
+ Preconditions.checkNotNull(outIndices, "outIndices");
+ synchronized (this) {
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ return nativeRetrieveAttributes(
+ mObject, parser.mParseState, inAttrs, outValues, outIndices);
+ }
+ }
+
+ long createTheme() {
+ synchronized (this) {
+ ensureValidLocked();
+ long themePtr = nativeThemeCreate(mObject);
+ incRefsLocked(themePtr);
+ return themePtr;
+ }
+ }
+
+ void releaseTheme(long themePtr) {
+ synchronized (this) {
+ nativeThemeDestroy(themePtr);
+ decRefsLocked(themePtr);
+ }
+ }
+
+ void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
+ synchronized (this) {
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ nativeThemeApplyStyle(mObject, themePtr, resId, force);
+ }
+ }
+
+ @Override
protected void finalize() throws Throwable {
- try {
- if (DEBUG_REFS && mNumRefs != 0) {
- Log.w(TAG, "AssetManager " + this
- + " finalized with non-zero refs: " + mNumRefs);
- if (mRefStacks != null) {
- for (RuntimeException e : mRefStacks.values()) {
- Log.w(TAG, "Reference from here", e);
- }
+ if (DEBUG_REFS && mNumRefs != 0) {
+ Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs);
+ if (mRefStacks != null) {
+ for (RuntimeException e : mRefStacks.values()) {
+ Log.w(TAG, "Reference from here", e);
}
}
- destroy();
- } finally {
- super.finalize();
+ }
+
+ if (mObject != 0) {
+ nativeDestroy(mObject);
}
}
-
+
+ /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread
+ safe and it does not rely on AssetManager once it has been created. It completely owns the
+ underlying Asset. */
public final class AssetInputStream extends InputStream {
+ private long mAssetNativePtr;
+ private long mLength;
+ private long mMarkPos;
+
/**
* @hide
*/
public final int getAssetInt() {
throw new UnsupportedOperationException();
}
+
/**
* @hide
*/
public final long getNativeAsset() {
- return mAsset;
+ return mAssetNativePtr;
}
- private AssetInputStream(long asset)
- {
- mAsset = asset;
- mLength = getAssetLength(asset);
+
+ private AssetInputStream(long assetNativePtr) {
+ mAssetNativePtr = assetNativePtr;
+ mLength = nativeAssetGetLength(assetNativePtr);
}
+
+ @Override
public final int read() throws IOException {
- return readAssetChar(mAsset);
+ ensureOpen();
+ return nativeAssetReadChar(mAssetNativePtr);
}
- public final boolean markSupported() {
- return true;
+
+ @Override
+ public final int read(@NonNull byte[] b) throws IOException {
+ ensureOpen();
+ Preconditions.checkNotNull(b, "b");
+ return nativeAssetRead(mAssetNativePtr, b, 0, b.length);
}
- public final int available() throws IOException {
- long len = getAssetRemainingLength(mAsset);
- return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
+
+ @Override
+ public final int read(@NonNull byte[] b, int off, int len) throws IOException {
+ ensureOpen();
+ Preconditions.checkNotNull(b, "b");
+ return nativeAssetRead(mAssetNativePtr, b, off, len);
}
- public final void close() throws IOException {
- synchronized (AssetManager.this) {
- if (mAsset != 0) {
- destroyAsset(mAsset);
- mAsset = 0;
- decRefsLocked(hashCode());
- }
- }
- }
- public final void mark(int readlimit) {
- mMarkPos = seekAsset(mAsset, 0, 0);
- }
- public final void reset() throws IOException {
- seekAsset(mAsset, mMarkPos, -1);
- }
- public final int read(byte[] b) throws IOException {
- return readAsset(mAsset, b, 0, b.length);
- }
- public final int read(byte[] b, int off, int len) throws IOException {
- return readAsset(mAsset, b, off, len);
- }
+
+ @Override
public final long skip(long n) throws IOException {
- long pos = seekAsset(mAsset, 0, 0);
- if ((pos+n) > mLength) {
- n = mLength-pos;
+ ensureOpen();
+ long pos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+ if ((pos + n) > mLength) {
+ n = mLength - pos;
}
if (n > 0) {
- seekAsset(mAsset, n, 0);
+ nativeAssetSeek(mAssetNativePtr, n, 0);
}
return n;
}
- protected void finalize() throws Throwable
- {
+ @Override
+ public final int available() throws IOException {
+ ensureOpen();
+ final long len = nativeAssetGetRemainingLength(mAssetNativePtr);
+ return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len;
+ }
+
+ @Override
+ public final boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public final void mark(int readlimit) {
+ ensureOpen();
+ mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+ }
+
+ @Override
+ public final void reset() throws IOException {
+ ensureOpen();
+ nativeAssetSeek(mAssetNativePtr, mMarkPos, -1);
+ }
+
+ @Override
+ public final void close() throws IOException {
+ if (mAssetNativePtr != 0) {
+ nativeAssetDestroy(mAssetNativePtr);
+ mAssetNativePtr = 0;
+
+ synchronized (AssetManager.this) {
+ decRefsLocked(hashCode());
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
close();
}
- private long mAsset;
- private long mLength;
- private long mMarkPos;
- }
-
- /**
- * Add an additional set of assets to the asset manager. This can be
- * either a directory or ZIP file. Not for use by applications. Returns
- * the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public final int addAssetPath(String path) {
- return addAssetPathInternal(path, false);
- }
-
- /**
- * Add an application assets to the asset manager and loading it as shared library.
- * This can be either a directory or ZIP file. Not for use by applications. Returns
- * the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public final int addAssetPathAsSharedLibrary(String path) {
- return addAssetPathInternal(path, true);
- }
-
- private final int addAssetPathInternal(String path, boolean appAsLib) {
- synchronized (this) {
- int res = addAssetPathNative(path, appAsLib);
- makeStringBlocks(mStringBlocks);
- return res;
+ private void ensureOpen() {
+ if (mAssetNativePtr == 0) {
+ throw new IllegalStateException("AssetInputStream is closed");
+ }
}
}
- private native final int addAssetPathNative(String path, boolean appAsLib);
-
- /**
- * Add an additional set of assets to the asset manager from an already open
- * FileDescriptor. Not for use by applications.
- * This does not give full AssetManager functionality for these assets,
- * since the origin of the file is not known for purposes of sharing,
- * overlay resolution, and other features. However it does allow you
- * to do simple access to the contents of the given fd as an apk file.
- * Performs a dup of the underlying fd, so you must take care of still closing
- * the FileDescriptor yourself (and can do that whenever you want).
- * Returns the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public int addAssetFd(FileDescriptor fd, String debugPathName) {
- return addAssetFdInternal(fd, debugPathName, false);
- }
-
- private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
- boolean appAsLib) {
- synchronized (this) {
- int res = addAssetFdNative(fd, debugPathName, appAsLib);
- makeStringBlocks(mStringBlocks);
- return res;
- }
- }
-
- private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
- boolean appAsLib);
-
- /**
- * Add a set of assets to overlay an already added set of assets.
- *
- * This is only intended for application resources. System wide resources
- * are handled before any Java code is executed.
- *
- * {@hide}
- */
-
- public final int addOverlayPath(String idmapPath) {
- synchronized (this) {
- int res = addOverlayPathNative(idmapPath);
- makeStringBlocks(mStringBlocks);
- return res;
- }
- }
-
- /**
- * See addOverlayPath.
- *
- * {@hide}
- */
- public native final int addOverlayPathNative(String idmapPath);
-
- /**
- * Add multiple sets of assets to the asset manager at once. See
- * {@link #addAssetPath(String)} for more information. Returns array of
- * cookies for each added asset with 0 indicating failure, or null if
- * the input array of paths is null.
- * {@hide}
- */
- public final int[] addAssetPaths(String[] paths) {
- if (paths == null) {
- return null;
- }
-
- int[] cookies = new int[paths.length];
- for (int i = 0; i < paths.length; i++) {
- cookies[i] = addAssetPath(paths[i]);
- }
-
- return cookies;
- }
-
/**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
- * {@hide}
+ * @hide
*/
- public native final boolean isUpToDate();
+ public boolean isUpToDate() {
+ for (ApkAssets apkAssets : getApkAssets()) {
+ if (!apkAssets.isUpToDate()) {
+ return false;
+ }
+ }
+ return true;
+ }
/**
* Get the locales that this asset manager contains data for.
@@ -784,7 +1130,12 @@
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
* and {@code CC} is a two letter country code.
*/
- public native final String[] getLocales();
+ public String[] getLocales() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLocales(mObject, false /*excludeSystem*/);
+ }
+ }
/**
* Same as getLocales(), except that locales that are only provided by the system (i.e. those
@@ -794,131 +1145,58 @@
* assets support Cherokee and French, getLocales() would return
* [Cherokee, English, French, German], while getNonSystemLocales() would return
* [Cherokee, French].
- * {@hide}
+ * @hide
*/
- public native final String[] getNonSystemLocales();
-
- /** {@hide} */
- public native final Configuration[] getSizeConfigurations();
+ public String[] getNonSystemLocales() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLocales(mObject, true /*excludeSystem*/);
+ }
+ }
/**
- * Change the configuation used when retrieving resources. Not for use by
+ * @hide
+ */
+ Configuration[] getSizeConfigurations() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetSizeConfigurations(mObject);
+ }
+ }
+
+ /**
+ * Change the configuration used when retrieving resources. Not for use by
* applications.
- * {@hide}
+ * @hide
*/
- public native final void setConfiguration(int mcc, int mnc, String locale,
- int orientation, int touchscreen, int density, int keyboard,
- int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
- int screenLayout, int uiMode, int colorMode, int majorVersion);
+ public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation,
+ int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
+ int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
+ int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
+ keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
+ smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
+ colorMode, majorVersion);
+ }
+ }
/**
- * Retrieve the resource identifier for the given resource name.
+ * @hide
*/
- /*package*/ native final int getResourceIdentifier(String name,
- String defType,
- String defPackage);
+ public SparseArray<String> getAssignedPackageIdentifiers() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetAssignedPackageIdentifiers(mObject);
+ }
+ }
- /*package*/ native final String getResourceName(int resid);
- /*package*/ native final String getResourcePackageName(int resid);
- /*package*/ native final String getResourceTypeName(int resid);
- /*package*/ native final String getResourceEntryName(int resid);
-
- private native final long openAsset(String fileName, int accessMode);
- private final native ParcelFileDescriptor openAssetFd(String fileName,
- long[] outOffsets) throws IOException;
- private native final long openNonAssetNative(int cookie, String fileName,
- int accessMode);
- private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
- String fileName, long[] outOffsets) throws IOException;
- private native final void destroyAsset(long asset);
- private native final int readAssetChar(long asset);
- private native final int readAsset(long asset, byte[] b, int off, int len);
- private native final long seekAsset(long asset, long offset, int whence);
- private native final long getAssetLength(long asset);
- private native final long getAssetRemainingLength(long asset);
-
- /** Returns true if the resource was found, filling in mRetStringBlock and
- * mRetData. */
- private native final int loadResourceValue(int ident, short density, TypedValue outValue,
- boolean resolve);
- /** Returns true if the resource was found, filling in mRetStringBlock and
- * mRetData. */
- private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
- boolean resolve);
- /*package*/ static final int STYLE_NUM_ENTRIES = 6;
- /*package*/ static final int STYLE_TYPE = 0;
- /*package*/ static final int STYLE_DATA = 1;
- /*package*/ static final int STYLE_ASSET_COOKIE = 2;
- /*package*/ static final int STYLE_RESOURCE_ID = 3;
-
- /* Offset within typed data array for native changingConfigurations. */
- static final int STYLE_CHANGING_CONFIGURATIONS = 4;
-
- /*package*/ static final int STYLE_DENSITY = 5;
- /*package*/ native static final void applyStyle(long theme,
- int defStyleAttr, int defStyleRes, long xmlParser,
- int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress);
- /*package*/ native static final boolean resolveAttrs(long theme,
- int defStyleAttr, int defStyleRes, int[] inValues,
- int[] inAttrs, int[] outValues, int[] outIndices);
- /*package*/ native final boolean retrieveAttributes(
- long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
- /*package*/ native final int getArraySize(int resource);
- /*package*/ native final int retrieveArray(int resource, int[] outValues);
- private native final int getStringBlockCount();
- private native final long getNativeStringBlock(int block);
-
- /**
- * {@hide}
- */
- public native final String getCookieName(int cookie);
-
- /**
- * {@hide}
- */
- public native final SparseArray<String> getAssignedPackageIdentifiers();
-
- /**
- * {@hide}
- */
- public native static final int getGlobalAssetCount();
-
- /**
- * {@hide}
- */
- public native static final String getAssetAllocations();
-
- /**
- * {@hide}
- */
- public native static final int getGlobalAssetManagerCount();
-
- private native final long newTheme();
- private native final void deleteTheme(long theme);
- /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
- /*package*/ native static final void copyTheme(long dest, long source);
- /*package*/ native static final void clearTheme(long theme);
- /*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
- TypedValue outValue,
- boolean resolve);
- /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);
- /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme);
-
- private native final long openXmlAssetNative(int cookie, String fileName);
-
- private native final String[] getArrayStringResource(int arrayRes);
- private native final int[] getArrayStringInfo(int arrayRes);
- /*package*/ native final int[] getArrayIntResource(int arrayRes);
- /*package*/ native final int[] getStyleAttributes(int themeRes);
-
- private native final void init(boolean isSystem);
- private native final void destroy();
-
- private final void incRefsLocked(long id) {
+ @GuardedBy("this")
+ private void incRefsLocked(long id) {
if (DEBUG_REFS) {
if (mRefStacks == null) {
- mRefStacks = new HashMap<Long, RuntimeException>();
+ mRefStacks = new HashMap<>();
}
RuntimeException ex = new RuntimeException();
ex.fillInStackTrace();
@@ -926,16 +1204,119 @@
}
mNumRefs++;
}
-
- private final void decRefsLocked(long id) {
+
+ @GuardedBy("this")
+ private void decRefsLocked(long id) {
if (DEBUG_REFS && mRefStacks != null) {
mRefStacks.remove(id);
}
mNumRefs--;
- //System.out.println("Dec streams: mNumRefs=" + mNumRefs
- // + " mReleased=" + mReleased);
- if (mNumRefs == 0) {
- destroy();
+ if (mNumRefs == 0 && mObject != 0) {
+ nativeDestroy(mObject);
+ mObject = 0;
+ mApkAssets = sEmptyApkAssets;
}
}
+
+ // AssetManager setup native methods.
+ private static native long nativeCreate();
+ private static native void nativeDestroy(long ptr);
+ private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
+ boolean invalidateCaches);
+ private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
+ @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
+ int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+ int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
+ int uiMode, int colorMode, int majorVersion);
+ private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
+ long ptr);
+
+ // File native methods.
+ private static native @Nullable String[] nativeList(long ptr, @NonNull String path)
+ throws IOException;
+ private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode);
+ private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr,
+ @NonNull String fileName, long[] outOffsets) throws IOException;
+ private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName,
+ int accessMode);
+ private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
+ @NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
+ private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+
+ // Primitive resource native methods.
+ private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
+ @NonNull TypedValue outValue, boolean resolveReferences);
+ private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId,
+ @NonNull TypedValue outValue);
+
+ private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr,
+ @StyleRes int resId);
+ private static native @Nullable String[] nativeGetResourceStringArray(long ptr,
+ @ArrayRes int resId);
+ private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr,
+ @ArrayRes int resId);
+ private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId);
+ private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId);
+ private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId,
+ @NonNull int[] outValues);
+
+ // Resource name/ID native methods.
+ private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name,
+ @Nullable String defType, @Nullable String defPackage);
+ private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid);
+ private static native @Nullable String nativeGetResourcePackageName(long ptr,
+ @AnyRes int resid);
+ private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid);
+ private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
+ private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
+ private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
+
+ // Style attribute retrieval native methods.
+ private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs,
+ long outValuesAddress, long outIndicesAddress);
+ private static native boolean nativeResolveAttrs(long ptr, long themePtr,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues,
+ @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
+ private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr,
+ @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
+
+ // Theme related native methods
+ private static native long nativeThemeCreate(long ptr);
+ private static native void nativeThemeDestroy(long themePtr);
+ private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
+ boolean force);
+ static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr);
+ static native void nativeThemeClear(long themePtr);
+ private static native int nativeThemeGetAttributeValue(long ptr, long themePtr,
+ @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve);
+ private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
+ String prefix);
+ static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
+
+ // AssetInputStream related native methods.
+ private static native void nativeAssetDestroy(long assetPtr);
+ private static native int nativeAssetReadChar(long assetPtr);
+ private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len);
+ private static native long nativeAssetSeek(long assetPtr, long offset, int whence);
+ private static native long nativeAssetGetLength(long assetPtr);
+ private static native long nativeAssetGetRemainingLength(long assetPtr);
+
+ private static native void nativeVerifySystemIdmaps();
+
+ // Global debug native methods.
+ /**
+ * @hide
+ */
+ public static native int getGlobalAssetCount();
+
+ /**
+ * @hide
+ */
+ public static native String getAssetAllocations();
+
+ /**
+ * @hide
+ */
+ public static native int getGlobalAssetManagerCount();
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index e173653c..d813382 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -590,7 +590,7 @@
*/
@NonNull
public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
- int[] res = mResourcesImpl.getAssets().getArrayIntResource(id);
+ int[] res = mResourcesImpl.getAssets().getResourceIntArray(id);
if (res != null) {
return res;
}
@@ -613,13 +613,13 @@
@NonNull
public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException {
final ResourcesImpl impl = mResourcesImpl;
- int len = impl.getAssets().getArraySize(id);
+ int len = impl.getAssets().getResourceArraySize(id);
if (len < 0) {
throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
}
TypedArray array = TypedArray.obtain(this, len);
- array.mLength = impl.getAssets().retrieveArray(id, array.mData);
+ array.mLength = impl.getAssets().getResourceArray(id, array.mData);
array.mIndices[0] = 0;
return array;
@@ -867,8 +867,9 @@
* @param theme The theme used to style the drawable attributes, may be {@code null}.
* @return Drawable An object that can be used to draw this resource.
* @throws NotFoundException Throws NotFoundException if the given ID does
- * not exist.
+ * not exist, or cannot be decoded.
*/
+ @NonNull
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
final TypedValue value = obtainTempTypedValue();
try {
@@ -980,7 +981,7 @@
* or multiple colors that can be selected based on a state.
* @deprecated Use {@link #getColorStateList(int, Theme)} instead.
*/
- @Nullable
+ @NonNull
@Deprecated
public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
final ColorStateList csl = getColorStateList(id, null);
@@ -1011,7 +1012,7 @@
* @return A themed ColorStateList object containing either a single solid
* color or multiple colors that can be selected based on a state.
*/
- @Nullable
+ @NonNull
public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
@@ -1024,7 +1025,7 @@
}
}
- @Nullable
+ @NonNull
ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme)
throws NotFoundException {
return mResourcesImpl.loadColorStateList(this, value, id, theme);
@@ -1033,7 +1034,7 @@
/**
* @hide
*/
- @Nullable
+ @NonNull
public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, @Nullable Theme theme) {
return mResourcesImpl.loadComplexColor(this, value, id, theme);
}
@@ -1139,6 +1140,7 @@
*
* @see #getXml
*/
+ @NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
@@ -1163,6 +1165,7 @@
*
* @see #getXml
*/
+ @NonNull
public XmlResourceParser getAnimation(@AnimatorRes @AnimRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "anim");
}
@@ -1188,6 +1191,7 @@
*
* @see android.util.AttributeSet
*/
+ @NonNull
public XmlResourceParser getXml(@XmlRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "xml");
}
@@ -1203,8 +1207,8 @@
* @return InputStream Access to the resource data.
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
- *
*/
+ @NonNull
public InputStream openRawResource(@RawRes int id) throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
@@ -1261,6 +1265,7 @@
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
+ @NonNull
public InputStream openRawResource(@RawRes int id, TypedValue value)
throws NotFoundException {
return mResourcesImpl.openRawResource(id, value);
@@ -1789,8 +1794,7 @@
// out the attributes from the XML file (applying type information
// contained in the resources and such).
XmlBlock.Parser parser = (XmlBlock.Parser)set;
- mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs,
- array.mData, array.mIndices);
+ mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices);
array.mXml = parser;
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 97cb78b..08a1613 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -168,7 +168,6 @@
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
- mAssets.ensureStringBlocks();
}
public DisplayAdjustments getDisplayAdjustments() {
@@ -543,7 +542,7 @@
}
}
- @Nullable
+ @NonNull
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
@@ -627,7 +626,7 @@
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
- dr = loadDrawableForCookie(wrapper, value, id, density, null);
+ dr = loadDrawableForCookie(wrapper, value, id, density);
}
// DrawableContainer' constant state has drawables instances. In order to leave the
// constant state intact in the cache, we need to create a new DrawableContainer after
@@ -754,8 +753,9 @@
/**
* Loads a drawable from XML or resources stream.
*/
+ @NonNull
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
- int id, int density, @Nullable Resources.Theme theme) {
+ int id, int density) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
@@ -774,22 +774,23 @@
}
}
- // For prelaod tracing.
+ // For preload tracing.
long startTime = 0;
int startBitmapCount = 0;
long startBitmapSize = 0;
- int startDrwableCount = 0;
+ int startDrawableCount = 0;
if (TRACE_FOR_DETAILED_PRELOAD) {
startTime = System.nanoTime();
startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
- startDrwableCount = sPreloadTracingNumLoadedDrawables;
+ startDrawableCount = sPreloadTracingNumLoadedDrawables;
}
if (DEBUG_LOAD) {
Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
}
+
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
@@ -804,7 +805,7 @@
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+ dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
@@ -834,7 +835,7 @@
final long loadedBitmapSize =
Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize;
final int loadedDrawables =
- sPreloadTracingNumLoadedDrawables - startDrwableCount;
+ sPreloadTracingNumLoadedDrawables - startDrawableCount;
sPreloadTracingNumLoadedDrawables++;
@@ -910,6 +911,7 @@
* first try to load CSL from the cache. If not found, try to get from the constant state.
* Last, parse the XML and generate the CSL.
*/
+ @NonNull
private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
TypedValue value, int id) {
final long key = (((long) value.assetCookie) << 32) | value.data;
@@ -929,17 +931,15 @@
complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
}
- if (complexColor != null) {
- complexColor.setBaseChangingConfigurations(value.changingConfigurations);
+ complexColor.setBaseChangingConfigurations(value.changingConfigurations);
- if (mPreloading) {
- if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
- 0, value.resourceId, "color")) {
- sPreloadedComplexColors.put(key, complexColor.getConstantState());
- }
- } else {
- cache.put(key, theme, complexColor.getConstantState());
+ if (mPreloading) {
+ if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
+ 0, value.resourceId, "color")) {
+ sPreloadedComplexColors.put(key, complexColor.getConstantState());
}
+ } else {
+ cache.put(key, theme, complexColor.getConstantState());
}
return complexColor;
}
@@ -985,7 +985,7 @@
return complexColor;
}
- @Nullable
+ @NonNull
ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Resources.Theme theme)
throws NotFoundException {
@@ -1045,7 +1045,7 @@
*
* @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
*/
- @Nullable
+ @NonNull
private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) {
if (value.string == null) {
@@ -1274,8 +1274,7 @@
void applyStyle(int resId, boolean force) {
synchronized (mKey) {
- AssetManager.applyThemeStyle(mTheme, resId, force);
-
+ mAssets.applyStyleToTheme(mTheme, resId, force);
mThemeResId = resId;
mKey.append(resId, force);
}
@@ -1284,7 +1283,7 @@
void setTo(ThemeImpl other) {
synchronized (mKey) {
synchronized (other.mKey) {
- AssetManager.copyTheme(mTheme, other.mTheme);
+ AssetManager.nativeThemeCopy(mTheme, other.mTheme);
mThemeResId = other.mThemeResId;
mKey.setTo(other.getKey());
@@ -1307,12 +1306,10 @@
// out the attributes from the XML file (applying type information
// contained in the resources and such).
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
- AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
- parser != null ? parser.mParseState : 0,
- attrs, attrs.length, array.mDataAddress, array.mIndicesAddress);
+ mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
+ array.mDataAddress, array.mIndicesAddress);
array.mTheme = wrapper;
array.mXml = parser;
-
return array;
}
}
@@ -1329,7 +1326,7 @@
}
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
- AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
+ mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
array.mTheme = wrapper;
array.mXml = null;
return array;
@@ -1349,14 +1346,14 @@
@Config int getChangingConfigurations() {
synchronized (mKey) {
final @NativeConfig int nativeChangingConfig =
- AssetManager.getThemeChangingConfigurations(mTheme);
+ AssetManager.nativeThemeGetChangingConfigurations(mTheme);
return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
}
}
public void dump(int priority, String tag, String prefix) {
synchronized (mKey) {
- AssetManager.dumpTheme(mTheme, priority, tag, prefix);
+ mAssets.dumpTheme(mTheme, priority, tag, prefix);
}
}
@@ -1385,13 +1382,13 @@
*/
void rebase() {
synchronized (mKey) {
- AssetManager.clearTheme(mTheme);
+ AssetManager.nativeThemeClear(mTheme);
// Reapply the same styles in the same order.
for (int i = 0; i < mKey.mCount; i++) {
final int resId = mKey.mResId[i];
final boolean force = mKey.mForce[i];
- AssetManager.applyThemeStyle(mTheme, resId, force);
+ mAssets.applyStyleToTheme(mTheme, resId, force);
}
}
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f33c751..cbb3c6d 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -61,6 +61,15 @@
return attrs;
}
+ // STYLE_ prefixed constants are offsets within the typed data array.
+ static final int STYLE_NUM_ENTRIES = 6;
+ static final int STYLE_TYPE = 0;
+ static final int STYLE_DATA = 1;
+ static final int STYLE_ASSET_COOKIE = 2;
+ static final int STYLE_RESOURCE_ID = 3;
+ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
+ static final int STYLE_DENSITY = 5;
+
private final Resources mResources;
private DisplayMetrics mMetrics;
private AssetManager mAssets;
@@ -78,7 +87,7 @@
private void resize(int len) {
mLength = len;
- final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ final int dataLen = len * STYLE_NUM_ENTRIES;
final int indicesLen = len + 1;
final VMRuntime runtime = VMRuntime.getRuntime();
if (mDataAddress == 0 || mData.length < dataLen) {
@@ -166,9 +175,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
@@ -203,9 +212,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
@@ -242,14 +251,13 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_STRING) {
- final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ final int cookie = data[index + STYLE_ASSET_COOKIE];
if (cookie < 0) {
- return mXml.getPooledString(
- data[index+AssetManager.STYLE_DATA]).toString();
+ return mXml.getPooledString(data[index + STYLE_DATA]).toString();
}
}
return null;
@@ -274,11 +282,11 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
- data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
if ((changingConfigs & ~allowedChangingConfigs) != 0) {
return null;
}
@@ -320,14 +328,14 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA] != 0;
+ return data[index + STYLE_DATA] != 0;
}
final TypedValue v = mValue;
@@ -359,14 +367,14 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
}
final TypedValue v = mValue;
@@ -396,16 +404,16 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FLOAT) {
- return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
+ return Float.intBitsToFloat(data[index + STYLE_DATA]);
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
}
final TypedValue v = mValue;
@@ -446,15 +454,15 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_STRING) {
final TypedValue value = mValue;
if (getValueAt(index, value)) {
@@ -498,7 +506,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -533,7 +541,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -564,15 +572,15 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -612,15 +620,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimension(
- data[index + AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -661,15 +668,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(
- data[index + AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -711,15 +717,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -755,16 +760,15 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -795,15 +799,14 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- data[index + AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
}
return defValue;
@@ -833,15 +836,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FRACTION) {
- return TypedValue.complexToFraction(
- data[index+AssetManager.STYLE_DATA], base, pbase);
+ return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -874,10 +876,10 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
- final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
+ if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) {
+ final int resid = data[index + STYLE_RESOURCE_ID];
if (resid != 0) {
return resid;
}
@@ -902,10 +904,10 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
- return data[index + AssetManager.STYLE_DATA];
+ if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+ return data[index + STYLE_DATA];
}
return defValue;
}
@@ -939,7 +941,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -975,7 +977,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -1006,7 +1008,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
return mResources.getTextArray(value.resourceId);
}
return null;
@@ -1027,7 +1029,7 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
+ return getValueAt(index * STYLE_NUM_ENTRIES, outValue);
}
/**
@@ -1043,8 +1045,8 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
- return mData[index + AssetManager.STYLE_TYPE];
+ index *= STYLE_NUM_ENTRIES;
+ return mData[index + STYLE_TYPE];
}
/**
@@ -1063,9 +1065,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
return type != TypedValue.TYPE_NULL;
}
@@ -1084,11 +1086,11 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
return type != TypedValue.TYPE_NULL
- || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+ || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
}
/**
@@ -1109,7 +1111,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
return value;
}
return null;
@@ -1181,16 +1183,16 @@
final int[] data = mData;
final int N = length();
for (int i = 0; i < N; i++) {
- final int index = i * AssetManager.STYLE_NUM_ENTRIES;
- if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ final int index = i * STYLE_NUM_ENTRIES;
+ if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
// Not an attribute, ignore.
continue;
}
// Null the entry so that we can safely call getZzz().
- data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
+ data[index + STYLE_TYPE] = TypedValue.TYPE_NULL;
- final int attr = data[index + AssetManager.STYLE_DATA];
+ final int attr = data[index + STYLE_DATA];
if (attr == 0) {
// Useless data, ignore.
continue;
@@ -1231,45 +1233,44 @@
final int[] data = mData;
final int N = length();
for (int i = 0; i < N; i++) {
- final int index = i * AssetManager.STYLE_NUM_ENTRIES;
- final int type = data[index + AssetManager.STYLE_TYPE];
+ final int index = i * STYLE_NUM_ENTRIES;
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
continue;
}
changingConfig |= ActivityInfo.activityInfoConfigNativeToJava(
- data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
}
return changingConfig;
}
private boolean getValueAt(int index, TypedValue outValue) {
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return false;
}
outValue.type = type;
- outValue.data = data[index+AssetManager.STYLE_DATA];
- outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
- outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
+ outValue.data = data[index + STYLE_DATA];
+ outValue.assetCookie = data[index + STYLE_ASSET_COOKIE];
+ outValue.resourceId = data[index + STYLE_RESOURCE_ID];
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
- data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
- outValue.density = data[index+AssetManager.STYLE_DENSITY];
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
+ outValue.density = data[index + STYLE_DENSITY];
outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
return true;
}
private CharSequence loadStringValueAt(int index) {
final int[] data = mData;
- final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ final int cookie = data[index + STYLE_ASSET_COOKIE];
if (cookie < 0) {
if (mXml != null) {
- return mXml.getPooledString(
- data[index+AssetManager.STYLE_DATA]);
+ return mXml.getPooledString(data[index + STYLE_DATA]);
}
return null;
}
- return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
+ return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
}
/** @hide */
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index e6b95741..d4ccffb 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.annotation.Nullable;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
@@ -33,7 +34,7 @@
*
* {@hide}
*/
-final class XmlBlock {
+final class XmlBlock implements AutoCloseable {
private static final boolean DEBUG=false;
public XmlBlock(byte[] data) {
@@ -48,6 +49,7 @@
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
}
+ @Override
public void close() {
synchronized (this) {
if (mOpen) {
@@ -478,13 +480,13 @@
* are doing! The given native object must exist for the entire lifetime
* of this newly creating XmlBlock.
*/
- XmlBlock(AssetManager assets, long xmlBlock) {
+ XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
mAssets = assets;
mNative = xmlBlock;
mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
}
- private final AssetManager mAssets;
+ private @Nullable final AssetManager mAssets;
private final long mNative;
/*package*/ final StringBlock mStrings;
private boolean mOpen = true;
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index b211700..dc60612 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -422,6 +422,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private boolean recycleConnectionLocked(SQLiteConnection connection,
AcquiredConnectionStatus status) {
if (status == AcquiredConnectionStatus.RECONFIGURE) {
@@ -531,6 +532,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void closeAvailableConnectionsAndLogExceptionsLocked() {
closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
@@ -541,6 +543,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private boolean closeAvailableConnectionLocked(int connectionId) {
final int count = mAvailableNonPrimaryConnections.size();
for (int i = count - 1; i >= 0; i--) {
@@ -562,6 +565,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
final int count = mAvailableNonPrimaryConnections.size();
for (int i = 0; i < count; i++) {
@@ -581,6 +585,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void closeExcessConnectionsAndLogExceptionsLocked() {
int availableCount = mAvailableNonPrimaryConnections.size();
while (availableCount-- > mMaxConnectionPoolSize - 1) {
@@ -591,6 +596,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
try {
connection.close(); // might throw
@@ -609,6 +615,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void reconfigureAllConnectionsLocked() {
if (mAvailablePrimaryConnection != null) {
try {
@@ -776,6 +783,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void cancelConnectionWaiterLocked(ConnectionWaiter waiter) {
if (waiter.mAssignedConnection != null || waiter.mException != null) {
// Waiter is done waiting but has not woken up yet.
@@ -848,6 +856,7 @@
}
// Can't throw.
+ @GuardedBy("mLock")
private void wakeConnectionWaitersLocked() {
// Unpark all waiters that have requests that we can fulfill.
// This method is designed to not throw runtime exceptions, although we might send
@@ -910,6 +919,7 @@
}
// Might throw.
+ @GuardedBy("mLock")
private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {
// If the primary connection is available, acquire it now.
SQLiteConnection connection = mAvailablePrimaryConnection;
@@ -935,6 +945,7 @@
}
// Might throw.
+ @GuardedBy("mLock")
private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(
String sql, int connectionFlags) {
// Try to acquire the next connection in the queue.
@@ -974,6 +985,7 @@
}
// Might throw.
+ @GuardedBy("mLock")
private void finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags) {
try {
final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index c1c0812..ae1f57d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2006,7 +2006,6 @@
* SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
* SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
* myDatabaseErrorHandler);
- * db.enableWriteAheadLogging();
* </pre></code>
* </p><p>
* Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging}
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index bae2d04..0be45a0 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -17,7 +17,6 @@
package android.hardware.camera2;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -26,6 +25,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* <p>The total assembled results of a single image capture from the image sensor.</p>
@@ -49,9 +49,9 @@
*
* <p>For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying
* physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata
- * for that physical camera. And its keys and values can be accessed by
- * {@link #getPhysicalCameraKey}. If all requested surfaces are for the logical camera, no
- * metadata for physical camera will be included.</p>
+ * for that physical camera. And the mapping between the physical camera id and result metadata
+ * can be accessed via {@link #getPhysicalCameraResults}. If all requested surfaces are for the
+ * logical camera, no metadata for physical camera will be included.</p>
*
* <p>{@link TotalCaptureResult} objects are immutable.</p>
*
@@ -62,7 +62,7 @@
private final List<CaptureResult> mPartialResults;
private final int mSessionId;
// The map between physical camera id and capture result
- private final HashMap<String, CameraMetadataNative> mPhysicalCaptureResults;
+ private final HashMap<String, CaptureResult> mPhysicalCaptureResults;
/**
* Takes ownership of the passed-in camera metadata and the partial results
@@ -83,10 +83,12 @@
mSessionId = sessionId;
- mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
+ mPhysicalCaptureResults = new HashMap<String, CaptureResult>();
for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
+ CaptureResult physicalResult = new CaptureResult(
+ onePhysicalResult.getCameraMetadata(), parent, extras);
mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
- onePhysicalResult.getCameraMetadata());
+ physicalResult);
}
}
@@ -101,7 +103,7 @@
mPartialResults = new ArrayList<>();
mSessionId = CameraCaptureSession.SESSION_ID_NONE;
- mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
+ mPhysicalCaptureResults = new HashMap<String, CaptureResult>();
}
/**
@@ -132,36 +134,20 @@
}
/**
- * Get a capture result field value for a particular physical camera id.
+ * Get the map between physical camera ids and their capture result metadata
*
- * <p>The field definitions can be found in {@link CaptureResult}.</p>
- *
- * <p>This function can be called for logical camera devices, which are devices that have
+ * <p>This function can be called for logical multi-camera devices, which are devices that have
* REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link
* CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of physical devices that
- * are backing the logical camera. The camera id included in physicalCameraId argument
- * selects an individual physical device, and returns its specific capture result field.</p>
+ * are backing the logical camera.</p>
*
- * <p>This function should only be called if one or more streams from the underlying
- * 'physicalCameraId' was requested by the corresponding capture request.</p>
- *
- * @throws IllegalArgumentException if the key was not valid, or the physicalCameraId is not
- * applicable to the current camera, or a stream from 'physicalCameraId' is not requested by the
- * corresponding capture request.
- *
- * @param key The result field to read.
- * @param physicalCameraId The physical camera the result originates from.
- *
- * @return The value of that key, or {@code null} if the field is not set.
- */
- @Nullable
- public <T> T getPhysicalCameraKey(Key<T> key, @NonNull String physicalCameraId) {
- if (!mPhysicalCaptureResults.containsKey(physicalCameraId)) {
- throw new IllegalArgumentException(
- "No TotalCaptureResult exists for physical camera " + physicalCameraId);
- }
+ * <p>If one or more streams from the underlying physical cameras were requested by the
+ * corresponding capture request, this function returns the result metadata for those physical
+ * cameras. Otherwise, an empty map is returned.</p>
- CameraMetadataNative physicalMetadata = mPhysicalCaptureResults.get(physicalCameraId);
- return physicalMetadata.get(key);
+ * @return unmodifiable map between physical camera ids and their capture result metadata
+ */
+ public Map<String, CaptureResult> getPhysicalCameraResults() {
+ return Collections.unmodifiableMap(mPhysicalCaptureResults);
}
}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index eb4bced..b205e2c 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -368,7 +368,7 @@
* desirable for the camera application to request streams from individual physical cameras.
* This call achieves it by mapping the OutputConfiguration to the physical camera id.</p>
*
- * <p>The valid physical camera id can be queried by {@link
+ * <p>The valid physical camera ids can be queried by {@link
* android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}.
* </p>
*
diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
index 00f3c36..1aa2557 100644
--- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java
+++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
@@ -47,6 +47,11 @@
private final float[] mStats;
/**
+ * Initialize day stats from the given state. The time spent in each of the bucket is
+ * initialized to 0.
+ *
+ * @param localDate The date for which stats are being tracked
+ * @param bucketBoundaries Bucket boundaries used from creating the buckets from
* @hide
*/
public AmbientBrightnessDayStats(@NonNull LocalDate localDate,
@@ -55,6 +60,11 @@
}
/**
+ * Initialize day stats from the given state
+ *
+ * @param localDate The date for which stats are being tracked
+ * @param bucketBoundaries Bucket boundaries used from creating the buckets from
+ * @param stats Time spent in each of the buckets (in seconds)
* @hide
*/
public AmbientBrightnessDayStats(@NonNull LocalDate localDate,
@@ -81,14 +91,26 @@
mStats = stats;
}
+ /**
+ * @return The {@link LocalDate} for which brightness stats are being tracked.
+ */
public LocalDate getLocalDate() {
return mLocalDate;
}
+ /**
+ * @return Aggregated stats of time spent (in seconds) in various buckets.
+ */
public float[] getStats() {
return mStats;
}
+ /**
+ * Returns the bucket boundaries (in lux) used for creating buckets. For eg., if the bucket
+ * boundaries array is {b1, b2, b3}, the buckets will be [b1, b2), [b2, b3), [b3, inf).
+ *
+ * @return The list of bucket boundaries.
+ */
public float[] getBucketBoundaries() {
return mBucketBoundaries;
}
@@ -169,7 +191,14 @@
dest.writeFloatArray(mStats);
}
- /** @hide */
+ /**
+ * Updates the stats by incrementing the time spent for the appropriate bucket based on ambient
+ * brightness reading.
+ *
+ * @param ambientBrightness Ambient brightness reading (in lux)
+ * @param durationSec Time spent with the given reading (in seconds)
+ * @hide
+ */
public void log(float ambientBrightness, float durationSec) {
int bucketIndex = getBucketIndex(ambientBrightness);
if (bucketIndex >= 0) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a817f33..017674f 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1818,9 +1818,9 @@
}
/**
- * Called when the input method window has been shown to the user, after
- * previously not being visible. This is done after all of the UI setup
- * for the window has occurred (creating its views etc).
+ * Called immediately before the input method window is shown to the user.
+ * You could override this to prepare for the window to be shown
+ * (update view structure etc).
*/
public void onWindowShown() {
// Intentionally empty
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 287bdc8..74d6470 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -26,6 +26,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
@@ -329,16 +330,34 @@
/**
* Returns a generated MAC address whose 24 least significant bits constituting the
- * NIC part of the address are randomly selected.
+ * NIC part of the address are randomly selected and has Google OUI base.
*
* The locally assigned bit is always set to 1. The multicast bit is always set to 0.
*
- * @return a random locally assigned MacAddress.
+ * @return a random locally assigned, unicast MacAddress with Google OUI.
+ *
+ * @hide
+ */
+ public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
+ return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
+ }
+
+ /**
+ * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
+ * unicast bit, are randomly selected.
+ *
+ * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+ *
+ * @return a random locally assigned, unicast MacAddress.
*
* @hide
*/
public static @NonNull MacAddress createRandomUnicastAddress() {
- return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random());
+ SecureRandom r = new SecureRandom();
+ long addr = r.nextLong() & VALID_LONG_MASK;
+ addr |= LOCALLY_ASSIGNED_MASK;
+ addr &= ~MULTICAST_MASK;
+ return new MacAddress(addr);
}
/**
@@ -355,8 +374,8 @@
*/
public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
- addr = addr | LOCALLY_ASSIGNED_MASK;
- addr = addr & ~MULTICAST_MASK;
+ addr |= LOCALLY_ASSIGNED_MASK;
+ addr &= ~MULTICAST_MASK;
return new MacAddress(addr);
}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index a734719..8b4f02e 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -323,4 +323,16 @@
public long getLongProperty(int id) {
return queryProperty(id);
}
+
+ /**
+ * Return true if the plugType given is wired
+ * @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
+ * or {@link #BATTERY_PLUGGED_WIRELESS}
+ *
+ * @return true if plugType is wired
+ * @hide
+ */
+ public static boolean isPlugWired(int plugType) {
+ return plugType == BATTERY_PLUGGED_USB || plugType == BATTERY_PLUGGED_AC;
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fd0e5ae..5d7cf1e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1537,6 +1537,7 @@
public static final int STATE2_BLUETOOTH_ON_FLAG = 1<<22;
public static final int STATE2_CAMERA_FLAG = 1<<21;
public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
+ public static final int STATE2_CELLULAR_HIGH_TX_POWER_FLAG = 1 << 19;
public static final int MOST_INTERESTING_STATES2 =
STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
@@ -2353,9 +2354,11 @@
WIFI_SUPPL_STATE_NAMES, WIFI_SUPPL_STATE_SHORT_NAMES),
new BitDescription(HistoryItem.STATE2_CAMERA_FLAG, "camera", "ca"),
new BitDescription(HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG, "ble_scan", "bles"),
+ new BitDescription(HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG,
+ "cellular_high_tx_power", "Chtp"),
new BitDescription(HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK,
HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT, "gps_signal_quality", "Gss",
- new String[] { "poor", "good"}, new String[] { "poor", "good"}),
+ new String[] { "poor", "good"}, new String[] { "poor", "good"})
};
public static final String[] HISTORY_EVENT_NAMES = new String[] {
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 4aadc5b..682fdb7 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -784,7 +784,7 @@
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
- private static final int CRASH_AT_SIZE = 5_000;
+ private static final int CRASH_AT_SIZE = 20_000;
/**
* We next warn when we exceed this bucket size.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 48f5684..fc78861 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -894,6 +894,14 @@
/**
* P.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li>{@link android.app.Service#startForeground Service.startForeground} requires
+ * that apps hold the permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE}.</li>
+ * </ul>
*/
public static final int P = CUR_DEVELOPMENT; // STOPSHIP Replace with the real version.
}
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index a565dee..fbdf27e 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -21,12 +21,6 @@
/** @hide */
@SystemApi
public interface IHwBinder {
- // These MUST match their corresponding libhwbinder/IBinder.h definition !!!
- /** @hide */
- public static final int FIRST_CALL_TRANSACTION = 1;
- /** @hide */
- public static final int FLAG_ONEWAY = 1;
-
/**
* Process a hwbinder transaction.
*
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index 8a27700..eae5217 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -55,9 +55,8 @@
/** Pull the specified data. Results will be sent to statsd when complete. */
StatsLogEventWrapper[] pullData(int pullCode);
- /** Send a broadcast to the specified pkg and class that it should getData now. */
- // TODO: Rename this and use a pending intent instead.
- oneway void sendBroadcast(String pkg, String cls);
+ /** Send a broadcast to the specified PendingIntent's as IBinder that it should getData now. */
+ oneway void sendDataBroadcast(in IBinder intentSender);
/**
* Requests StatsCompanionService to send a broadcast using the given intentSender
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 679b49d..682a24f1 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -81,12 +81,26 @@
/**
* Sets a configuration with the specified config key and subscribes to updates for this
* configuration key. Broadcasts will be sent if this configuration needs to be collected.
- * The configuration must be a wire-encoded StatsdConfig. The caller specifies the name of the
- * package and class that should receive these broadcasts.
+ * The configuration must be a wire-encoded StatsDConfig. The receiver for this data is
+ * registered in a separate function.
*
* Returns if this configuration was correctly registered.
*/
- boolean addConfiguration(in long configKey, in byte[] config, in String pkg, in String cls);
+ boolean addConfiguration(in long configKey, in byte[] config);
+
+ /**
+ * Registers the given pending intent for this config key. This intent is invoked when the
+ * memory consumed by the metrics for this configuration approach the pre-defined limits. There
+ * can be at most one listener per config key.
+ *
+ * Returns if this listener was correctly registered.
+ */
+ boolean setDataFetchOperation(long configKey, in IBinder intentSender);
+
+ /**
+ * Removes the data fetch operation for the specified configuration.
+ */
+ boolean removeDataFetchOperation(long configKey);
/**
* Removes the configuration with the matching config key. No-op if this config key does not
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2093cec..7e7af1a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -943,6 +943,20 @@
* @see #getUserRestrictions()
*/
public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
+
+ /**
+ * Specifies whether the user is allowed to print.
+ *
+ * This restriction can be set by device or profile owner.
+ *
+ * The default value is {@code false}.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_PRINTING = "no_printing";
+
/**
* Application restriction key that is used to indicate the pending arrival
* of real restrictions for the app.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index b6f16a7..e9b4853 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,10 +16,15 @@
package android.os;
-import android.hardware.vibrator.V1_0.Constants.EffectStrength;
-import android.hardware.vibrator.V1_1.Constants.Effect_1_1;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.vibrator.V1_0.EffectStrength;
+import android.hardware.vibrator.V1_2.Effect;
+import android.net.Uri;
import android.util.MathUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Arrays;
/**
@@ -49,7 +54,7 @@
* @see #get(int)
* @hide
*/
- public static final int EFFECT_CLICK = Effect_1_1.CLICK;
+ public static final int EFFECT_CLICK = Effect.CLICK;
/**
* A double click effect.
@@ -57,14 +62,62 @@
* @see #get(int)
* @hide
*/
- public static final int EFFECT_DOUBLE_CLICK = Effect_1_1.DOUBLE_CLICK;
+ public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
/**
* A tick effect.
* @see #get(int)
* @hide
*/
- public static final int EFFECT_TICK = Effect_1_1.TICK;
+ public static final int EFFECT_TICK = Effect.TICK;
+
+ /**
+ * A thud effect.
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_THUD = Effect.THUD;
+
+ /**
+ * A pop effect.
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_POP = Effect.POP;
+
+ /**
+ * A heavy click effect.
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
+
+
+ /**
+ * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
+ * pattern that can be played as a ringtone with any audio, depending on the device.
+ *
+ * @see #get(Uri, Context)
+ * @hide
+ */
+ @VisibleForTesting
+ public static final int[] RINGTONES = {
+ Effect.RINGTONE_1,
+ Effect.RINGTONE_2,
+ Effect.RINGTONE_3,
+ Effect.RINGTONE_4,
+ Effect.RINGTONE_5,
+ Effect.RINGTONE_6,
+ Effect.RINGTONE_7,
+ Effect.RINGTONE_8,
+ Effect.RINGTONE_9,
+ Effect.RINGTONE_10,
+ Effect.RINGTONE_11,
+ Effect.RINGTONE_12,
+ Effect.RINGTONE_13,
+ Effect.RINGTONE_14,
+ Effect.RINGTONE_15
+ };
/** @hide to prevent subclassing from outside of the framework */
public VibrationEffect() { }
@@ -198,6 +251,37 @@
return effect;
}
+ /**
+ * Get a predefined vibration effect associated with a given URI.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * @param uri The URI associated with the haptic effect.
+ * @param context The context used to get the URI to haptic effect association.
+ *
+ * @return The desired effect, or {@code null} if there's no associated effect.
+ *
+ * @hide
+ */
+ @Nullable
+ public static VibrationEffect get(Uri uri, Context context) {
+ String[] uris = context.getResources().getStringArray(
+ com.android.internal.R.array.config_ringtoneEffectUris);
+ for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
+ if (uris[i] == null) {
+ continue;
+ }
+ if (Uri.parse(uris[i]).equals(uri)) {
+ return get(RINGTONES[i]);
+ }
+ }
+ return null;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -548,10 +632,15 @@
case EFFECT_CLICK:
case EFFECT_DOUBLE_CLICK:
case EFFECT_TICK:
+ case EFFECT_THUD:
+ case EFFECT_POP:
+ case EFFECT_HEAVY_CLICK:
break;
default:
- throw new IllegalArgumentException(
- "Unknown prebaked effect type (value=" + mEffectId + ")");
+ if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect type (value=" + mEffectId + ")");
+ }
}
if (!isValidEffectStrength(mEffectStrength)) {
throw new IllegalArgumentException(
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 12a495b..fb22194 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -80,4 +80,11 @@
* ("28", ["libjpeg.so", "libbase.so"])]
*/
public static native Map<String, String[]> getVndkSnapshots();
+
+ /**
+ * @return target FCM version, a number specified in the device manifest
+ * indicating the FCM version that the device manifest implements. Null if
+ * device manifest doesn't specify this number (for legacy devices).
+ */
+ public static native Long getTargetFrameworkCompatibilityMatrixVersion();
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 670f794..4a97640 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -61,18 +61,27 @@
/**
* The name of the socket used to communicate with the primary zygote.
*/
- private final String mSocket;
+ private final LocalSocketAddress mSocket;
/**
* The name of the secondary (alternate ABI) zygote socket.
*/
- private final String mSecondarySocket;
+ private final LocalSocketAddress mSecondarySocket;
public ZygoteProcess(String primarySocket, String secondarySocket) {
+ this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED),
+ new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED));
+ }
+
+ public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) {
mSocket = primarySocket;
mSecondarySocket = secondarySocket;
}
+ public LocalSocketAddress getPrimarySocketAddress() {
+ return mSocket;
+ }
+
/**
* State for communicating with the zygote process.
*/
@@ -92,14 +101,13 @@
this.abiList = abiList;
}
- public static ZygoteState connect(String socketAddress) throws IOException {
+ public static ZygoteState connect(LocalSocketAddress address) throws IOException {
DataInputStream zygoteInputStream = null;
BufferedWriter zygoteWriter = null;
final LocalSocket zygoteSocket = new LocalSocket();
try {
- zygoteSocket.connect(new LocalSocketAddress(socketAddress,
- LocalSocketAddress.Namespace.RESERVED));
+ zygoteSocket.connect(address);
zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
@@ -115,8 +123,8 @@
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
- Log.i("Zygote", "Process: zygote socket " + socketAddress + " opened, supported ABIS: "
- + abiListString);
+ Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/"
+ + address.getName() + " opened, supported ABIS: " + abiListString);
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
@@ -514,9 +522,19 @@
* @param socketName The name of the socket to connect to.
*/
public static void waitForConnectionToZygote(String socketName) {
+ final LocalSocketAddress address =
+ new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED);
+ waitForConnectionToZygote(address);
+ }
+
+ /**
+ * Try connecting to the Zygote over and over again until we hit a time-out.
+ * @param address The name of the socket to connect to.
+ */
+ public static void waitForConnectionToZygote(LocalSocketAddress address) {
for (int n = 20; n >= 0; n--) {
try {
- final ZygoteState zs = ZygoteState.connect(socketName);
+ final ZygoteState zs = ZygoteState.connect(address);
zs.close();
return;
} catch (IOException ioe) {
@@ -529,6 +547,6 @@
} catch (InterruptedException ie) {
}
}
- Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + socketName);
+ Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName());
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 567127a..84996e0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7396,7 +7396,8 @@
*/
public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
- private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = BOOLEAN_VALIDATOR;
+ private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR =
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
/**
* Control the color temperature of Night Display, represented in Kelvin.
@@ -8389,10 +8390,10 @@
private static final Validator POWER_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
/**
- * URI for the "wireless charging started" sound.
+ * URI for the "wireless charging started" and "wired charging started" sound.
* @hide
*/
- public static final String WIRELESS_CHARGING_STARTED_SOUND =
+ public static final String CHARGING_STARTED_SOUND =
"wireless_charging_started_sound";
/**
@@ -10271,6 +10272,20 @@
public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants";
/**
+ * An integer to show the version of the anomaly config. Ex: 1, which means
+ * current version is 1.
+ * @hide
+ */
+ public static final String ANOMALY_CONFIG_VERSION = "anomaly_config_version";
+
+ /**
+ * A base64-encoded string represents anomaly stats config, used for
+ * {@link android.app.StatsManager}.
+ * @hide
+ */
+ public static final String ANOMALY_CONFIG = "anomaly_config";
+
+ /**
* Always on display(AOD) specific settings
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -10301,16 +10316,6 @@
public static final String SYS_VDSO = "sys_vdso";
/**
- * Uid CpuPower global setting. This links the uid.cpupower system property.
- * The following values are supported:
- * 0 -> /proc/uid_cpupower/* are disabled
- * 1 -> /proc/uid_cpupower/* are enabled
- * Any other value defaults to enabled.
- * @hide
- */
- public static final String UID_CPUPOWER = "uid_cpupower";
-
- /**
* An integer to reduce the FPS by this factor. Only for experiments. Need to reboot the
* device for this setting to take full effect.
*
@@ -10319,6 +10324,15 @@
public static final String FPS_DEVISOR = "fps_divisor";
/**
+ * Flag to enable or disable display panel low power mode (lpm)
+ * false -> Display panel power saving mode is disabled.
+ * true -> Display panel power saving mode is enabled.
+ *
+ * @hide
+ */
+ public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index 29c6cea..7383de7 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -44,6 +44,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.Map;
/**
@@ -247,4 +248,13 @@
&& !mCompatibilityPackages.isEmpty()).append("]");
return builder.toString();
}
+
+ /**
+ * Dumps it!
+ */
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("Component: "); pw.println(getServiceInfo().getComponentName());
+ pw.print(prefix); pw.print("Settings: "); pw.println(mSettingsActivity);
+ pw.print(prefix); pw.print("Compat packages: "); pw.println(mCompatibilityPackages);
+ }
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index b7b2b2d..422e36b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1343,6 +1343,7 @@
/**
* @hide
*/
+ @GuardedBy("mLock")
public final void applyUpdateLocked(NotificationRankingUpdate update) {
mRankingMap = new RankingMap(update);
}
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
index 0f85e1f..9f2182e 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -1,4 +1,3 @@
siyamed@google.com
nona@google.com
clarabayarri@google.com
-toki@google.com
diff --git a/core/java/android/transition/ArcMotion.java b/core/java/android/transition/ArcMotion.java
index da14834..172c837 100644
--- a/core/java/android/transition/ArcMotion.java
+++ b/core/java/android/transition/ArcMotion.java
@@ -216,7 +216,13 @@
boolean isMovingUpwards = startY > endY;
- if ((Math.abs(deltaX) < Math.abs(deltaY))) {
+ if (deltaY == 0) {
+ ex = dx;
+ ey = dy + (Math.abs(deltaX) * 0.5f * mMinimumHorizontalTangent);
+ } else if (deltaX == 0) {
+ ex = dx + (Math.abs(deltaY) * 0.5f * mMinimumVerticalTangent);
+ ey = dy;
+ } else if ((Math.abs(deltaX) < Math.abs(deltaY))) {
// Similar triangles bfa and bde mean that (ab/fb = eb/bd)
// Therefore, eb = ab * bd / fb
// ab = hypotenuse
@@ -254,7 +260,7 @@
float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent;
float newArcDistance2 = 0;
- if (arcDist2 < minimumArcDist2) {
+ if (arcDist2 != 0 && arcDist2 < minimumArcDist2) {
newArcDistance2 = minimumArcDist2;
} else if (arcDist2 > maximumArcDist2) {
newArcDistance2 = maximumArcDist2;
diff --git a/core/java/android/util/ByteStringUtils.java b/core/java/android/util/ByteStringUtils.java
index 333208d..f6460ad 100644
--- a/core/java/android/util/ByteStringUtils.java
+++ b/core/java/android/util/ByteStringUtils.java
@@ -22,61 +22,63 @@
* @hide
*/
public final class ByteStringUtils {
- private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+ private static final char[] HEX_LOWERCASE_ARRAY = "0123456789abcdef".toCharArray();
+ private static final char[] HEX_UPPERCASE_ARRAY = "0123456789ABCDEF".toCharArray();
- private ByteStringUtils() {
+ private ByteStringUtils() {
/* hide constructor */
- }
-
- /**
- * Returns the hex encoded string representation of bytes.
- * @param bytes Byte array to encode.
- * @return Hex encoded string representation of bytes.
- */
- public static String toHexString(byte[] bytes) {
- if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
- return null;
}
- final int byteLength = bytes.length;
- final int charCount = 2 * byteLength;
- final char[] chars = new char[charCount];
+ /**
+ * Returns the hex encoded string representation of bytes.
+ * @param bytes Byte array to encode.
+ * @return Hex encoded string representation of bytes.
+ */
+ public static String toHexString(byte[] bytes) {
+ if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
+ return null;
+ }
- for (int i = 0; i < byteLength; i++) {
- final int byteHex = bytes[i] & 0xFF;
- chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
- chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
- }
- return new String(chars);
- }
+ final int byteLength = bytes.length;
+ final int charCount = 2 * byteLength;
+ final char[] chars = new char[charCount];
- /**
- * Returns the decoded byte array representation of str.
- * @param str Hex encoded string to decode.
- * @return Decoded byte array representation of str.
- */
- public static byte[] fromHexToByteArray(String str) {
- if (str == null || str.length() == 0 || str.length() % 2 != 0) {
- return null;
+ for (int i = 0; i < byteLength; i++) {
+ final int byteHex = bytes[i] & 0xFF;
+ chars[i * 2] = HEX_UPPERCASE_ARRAY[byteHex >>> 4];
+ chars[i * 2 + 1] = HEX_UPPERCASE_ARRAY[byteHex & 0x0F];
+ }
+ return new String(chars);
}
- final char[] chars = str.toCharArray();
- final int charLength = chars.length;
- final byte[] bytes = new byte[charLength / 2];
+ /**
+ * Returns the decoded byte array representation of str.
+ * @param str Hex encoded string to decode.
+ * @return Decoded byte array representation of str.
+ */
+ public static byte[] fromHexToByteArray(String str) {
+ if (str == null || str.length() == 0 || str.length() % 2 != 0) {
+ return null;
+ }
- for (int i = 0; i < bytes.length; i++) {
- bytes[i] =
- (byte)(((getIndex(chars[i * 2]) << 4) & 0xF0) | (getIndex(chars[i * 2 + 1]) & 0x0F));
- }
- return bytes;
- }
+ final char[] chars = str.toCharArray();
+ final int charLength = chars.length;
+ final byte[] bytes = new byte[charLength / 2];
- private static int getIndex(char c) {
- for (int i = 0; i < HEX_ARRAY.length; i++) {
- if (HEX_ARRAY[i] == c) {
- return i;
- }
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] =
+ (byte) (((getIndex(chars[i * 2]) << 4) & 0xF0)
+ | (getIndex(chars[i * 2 + 1]) & 0x0F));
+ }
+ return bytes;
}
- return -1;
- }
+
+ private static int getIndex(char c) {
+ for (int i = 0; i < HEX_UPPERCASE_ARRAY.length; i++) {
+ if (HEX_UPPERCASE_ARRAY[i] == c || HEX_LOWERCASE_ARRAY[i] == c) {
+ return i;
+ }
+ }
+ return -1;
+ }
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 410cdc6..1ead0b4 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -37,13 +37,13 @@
private static final Map<String, String> DEFAULT_FLAGS;
static {
DEFAULT_FLAGS = new HashMap<>();
- DEFAULT_FLAGS.put("device_info_v2", "true");
DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
DEFAULT_FLAGS.put("settings_battery_v2", "true");
DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
- DEFAULT_FLAGS.put("settings_about_phone_v2", "false");
+ DEFAULT_FLAGS.put("settings_about_phone_v2", "true");
DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
+ DEFAULT_FLAGS.put("settings_data_usage_v2", "false");
}
/**
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
deleted file mode 100644
index 51fb18a..0000000
--- a/core/java/android/util/StatsManager.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.util;
-
-import android.Manifest;
-import android.annotation.RequiresPermission;
-import android.os.IBinder;
-import android.os.IStatsManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-
-/*
- *
- *
- *
- *
- * THIS ENTIRE FILE IS ONLY TEMPORARY TO PREVENT BREAKAGES OF DEPENDENCIES ON OLD APIS.
- * The new StatsManager is to be found in android.app.StatsManager.
- * TODO: Delete this file!
- *
- *
- *
- *
- */
-
-
-/**
- * API for StatsD clients to send configurations and retrieve data.
- *
- * @hide
- */
-public class StatsManager {
- IStatsManager mService;
- private static final String TAG = "StatsManager";
-
- /**
- * Constructor for StatsManagerClient.
- *
- * @hide
- */
- public StatsManager() {
- }
-
- /**
- * Temporary to prevent build failures. Will be deleted.
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when adding configuration");
- return false;
- }
- return service.addConfiguration(Long.parseLong(configKey), config, pkg, cls);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when adding configuration");
- return false;
- }
- }
- }
-
- /**
- * Clients can send a configuration and simultaneously registers the name of a broadcast
- * receiver that listens for when it should request data.
- *
- * @param configKey An arbitrary integer that allows clients to track the configuration.
- * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
- * dependencies eg, conditions and matchers).
- * @param pkg The package name to receive the broadcast.
- * @param cls The name of the class that receives the broadcast.
- * @return true if successful
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when adding configuration");
- return false;
- }
- return service.addConfiguration(configKey, config, pkg, cls);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when adding configuration");
- return false;
- }
- }
- }
-
- /**
- * Temporary to prevent build failures. Will be deleted.
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public boolean removeConfiguration(String configKey) {
- // To prevent breakages of old dependencies.
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when removing configuration");
- return false;
- }
- return service.removeConfiguration(Long.parseLong(configKey));
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when removing configuration");
- return false;
- }
- }
- }
-
- /**
- * Remove a configuration from logging.
- *
- * @param configKey Configuration key to remove.
- * @return true if successful
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public boolean removeConfiguration(long configKey) {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when removing configuration");
- return false;
- }
- return service.removeConfiguration(configKey);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when removing configuration");
- return false;
- }
- }
- }
-
- /**
- * Temporary to prevent build failures. Will be deleted.
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public byte[] getData(String configKey) {
- // TODO: remove this and all other methods with String-based config keys.
- // To prevent build breakages of dependencies.
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting data");
- return null;
- }
- return service.getData(Long.parseLong(configKey));
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting data");
- return null;
- }
- }
- }
-
- /**
- * Clients can request data with a binder call. This getter is destructive and also clears
- * the retrieved metrics from statsd memory.
- *
- * @param configKey Configuration key to retrieve data from.
- * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public byte[] getData(long configKey) {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting data");
- return null;
- }
- return service.getData(configKey);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting data");
- return null;
- }
- }
- }
-
- /**
- * Clients can request metadata for statsd. Will contain stats across all configurations but not
- * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
- * This getter is not destructive and will not reset any metrics/counters.
- *
- * @return Serialized StatsdStatsReport proto. Returns null on failure.
- */
- @RequiresPermission(Manifest.permission.DUMP)
- public byte[] getMetadata() {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting metadata");
- return null;
- }
- return service.getMetadata();
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting metadata");
- return null;
- }
- }
- }
-
- private class StatsdDeathRecipient implements IBinder.DeathRecipient {
- @Override
- public void binderDied() {
- synchronized (this) {
- mService = null;
- }
- }
- }
-
- private IStatsManager getIStatsManagerLocked() throws RemoteException {
- if (mService != null) {
- return mService;
- }
- mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
- if (mService != null) {
- mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
- }
- return mService;
- }
-}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 37e9815..7251b71 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -20,6 +20,7 @@
import static android.view.DisplayInfoProto.APP_WIDTH;
import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
+import static android.view.DisplayInfoProto.NAME;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -685,6 +686,7 @@
protoOutputStream.write(LOGICAL_HEIGHT, logicalHeight);
protoOutputStream.write(APP_WIDTH, appWidth);
protoOutputStream.write(APP_HEIGHT, appHeight);
+ protoOutputStream.write(NAME, name);
protoOutputStream.end(token);
}
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index ea6226b..69973e6 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -16,6 +16,7 @@
package android.view;
+import android.graphics.Rect;
import android.view.RemoteAnimationTarget;
import android.view.IRecentsAnimationController;
@@ -28,15 +29,26 @@
oneway interface IRecentsAnimationRunner {
/**
- * Called when the system is ready for the handler to start animating all the visible tasks.
+ * Deprecated, to be removed once Launcher updates
*/
void onAnimationStart(in IRecentsAnimationController controller,
- in RemoteAnimationTarget[] apps);
+ in RemoteAnimationTarget[] apps) = 0;
/**
* Called when the system needs to cancel the current animation. This can be due to the
* wallpaper not drawing in time, or the handler not finishing the animation within a predefined
* amount of time.
*/
- void onAnimationCanceled();
+ void onAnimationCanceled() = 1;
+
+ /**
+ * Called when the system is ready for the handler to start animating all the visible tasks.
+ *
+ * @param homeContentInsets The current home app content insets
+ * @param minimizedHomeBounds Specifies the bounds of the minimized home app, will be
+ * {@code null} if the device is not currently in split screen
+ */
+ void onAnimationStart_New(in IRecentsAnimationController controller,
+ in RemoteAnimationTarget[] apps, in Rect homeContentInsets,
+ in Rect minimizedHomeBounds) = 2;
}
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index c28c389..facf575 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -79,6 +79,11 @@
public final Rect clipRect;
/**
+ * The insets of the main app window.
+ */
+ public final Rect contentInsets;
+
+ /**
* The index of the element in the tree in prefix order. This should be used for z-layering
* to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
* happen.
@@ -105,13 +110,14 @@
public final WindowConfiguration windowConfiguration;
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
- Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds,
- WindowConfiguration windowConfig) {
+ Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
+ Rect sourceContainerBounds, WindowConfiguration windowConfig) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
this.isTranslucent = isTranslucent;
this.clipRect = new Rect(clipRect);
+ this.contentInsets = new Rect(contentInsets);
this.prefixOrderIndex = prefixOrderIndex;
this.position = new Point(position);
this.sourceContainerBounds = new Rect(sourceContainerBounds);
@@ -124,6 +130,7 @@
leash = in.readParcelable(null);
isTranslucent = in.readBoolean();
clipRect = in.readParcelable(null);
+ contentInsets = in.readParcelable(null);
prefixOrderIndex = in.readInt();
position = in.readParcelable(null);
sourceContainerBounds = in.readParcelable(null);
@@ -142,6 +149,7 @@
dest.writeParcelable(leash, 0 /* flags */);
dest.writeBoolean(isTranslucent);
dest.writeParcelable(clipRect, 0 /* flags */);
+ dest.writeParcelable(contentInsets, 0 /* flags */);
dest.writeInt(prefixOrderIndex);
dest.writeParcelable(position, 0 /* flags */);
dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 79fc134..5de25ba 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+
import static java.lang.Math.max;
import android.animation.AnimatorInflater;
@@ -906,6 +907,13 @@
*/
private static boolean sThrowOnInvalidFloatProperties;
+ /**
+ * Prior to P, {@code #startDragAndDrop} accepts a builder which produces an empty drag shadow.
+ * Currently zero size SurfaceControl cannot be created thus we create a dummy 1x1 surface
+ * instead.
+ */
+ private static boolean sAcceptZeroSizeDragShadow;
+
/** @hide */
@IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
@Retention(RetentionPolicy.SOURCE)
@@ -4798,6 +4806,7 @@
Canvas.sCompatibilityRestore = targetSdkVersion < Build.VERSION_CODES.M;
Canvas.sCompatibilitySetBitmap = targetSdkVersion < Build.VERSION_CODES.O;
+ Canvas.setCompatibilityVersion(targetSdkVersion);
// In M and newer, our widgets can pass a "hint" value in the size
// for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
@@ -4840,6 +4849,8 @@
sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P;
+ sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
+
sCompatibilityDone = true;
}
}
@@ -8378,7 +8389,7 @@
AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
boolean forAutofill) {
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
- null, null, null);
+ null, null, info.getViewIdResourceName());
Rect rect = structure.getTempRect();
info.getBoundsInParent(rect);
structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
@@ -8418,6 +8429,13 @@
CharSequence cname = info.getClassName();
structure.setClassName(cname != null ? cname.toString() : null);
structure.setContentDescription(info.getContentDescription());
+ if (forAutofill) {
+ final int maxTextLength = info.getMaxTextLength();
+ if (maxTextLength != -1) {
+ structure.setMaxTextLength(maxTextLength);
+ }
+ structure.setHint(info.getHintText());
+ }
if ((info.getText() != null || info.getError() != null)) {
structure.setText(info.getText(), info.getTextSelectionStart(),
info.getTextSelectionEnd());
@@ -8428,7 +8446,8 @@
final AutofillValue autofillValue = AutofillValue.forText(structure.getText());
structure.setAutofillValue(autofillValue);
if (info.isPassword()) {
- structure.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ structure.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
} else {
structure.setDataIsSensitive(false);
@@ -23619,8 +23638,7 @@
* constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)}
* and {@link #onDrawShadow(Canvas)} methods are also overridden in order
* to supply the drag shadow's dimensions and appearance without
- * reference to any View object. If they are not overridden, then the result is an
- * invisible drag shadow.
+ * reference to any View object.
*/
public DragShadowBuilder() {
mView = new WeakReference<View>(null);
@@ -23774,6 +23792,9 @@
// Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
// does not accept zero size surface.
if (shadowSize.x == 0 || shadowSize.y == 0) {
+ if (!sAcceptZeroSizeDragShadow) {
+ throw new IllegalStateException("Drag shadow dimensions must be positive");
+ }
shadowSize.x = 1;
shadowSize.y = 1;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 273f097..8e60a72 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3971,15 +3971,7 @@
}
/**
- * Layout debugging code which draws rectangles around layout params.
- *
- * <p>This function is called automatically when the developer setting is enabled.<p/>
- *
- * <p>It is strongly advised to only call this function from debug builds as there is
- * a risk of leaking unwanted layout information.<p/>
- *
- * @param canvas the canvas on which to draw
- * @param paint the paint used to draw through
+ * @hide
*/
protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
for (int i = 0; i < getChildCount(); i++) {
@@ -3989,19 +3981,7 @@
}
/**
- * Layout debugging code which draws rectangles around:
- * <ul>
- * <li>optical bounds<li/>
- * <li>margins<li/>
- * <li>clip bounds<li/>
- * <ul/>
- *
- * <p>This function is called automatically when the developer setting is enabled.<p/>
- *
- * <p>It is strongly advised to only call this function from debug builds as there is
- * a risk of leaking unwanted layout information.<p/>
- *
- * @param canvas the canvas on which to draw
+ * @hide
*/
protected void onDebugDraw(Canvas canvas) {
Paint paint = getDebugPaint();
@@ -7732,14 +7712,10 @@
/**
* Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters.
*
- * <p>This function is called automatically when the developer setting is enabled.<p/>
- *
- * <p>It is strongly advised to only call this function from debug builds as there is
- * a risk of leaking unwanted layout information.<p/>
- *
* @param view the view that contains these layout parameters
* @param canvas the canvas on which to draw
- * @param paint the paint used to draw through
+ *
+ * @hide
*/
public void onDebugDraw(View view, Canvas canvas, Paint paint) {
}
@@ -8243,6 +8219,9 @@
return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL);
}
+ /**
+ * @hide
+ */
@Override
public void onDebugDraw(View view, Canvas canvas, Paint paint) {
Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 23e7d61..417a725 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3173,6 +3173,15 @@
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ writeToParcelNoRecycle(parcel, flags);
+ // Since instances of this class are fetched via synchronous i.e. blocking
+ // calls in IPCs we always recycle as soon as the instance is marshaled.
+ recycle();
+ }
+
+ /** @hide */
+ @TestApi
+ public void writeToParcelNoRecycle(Parcel parcel, int flags) {
// Write bit set of indices of fields with values differing from default
long nonDefaultFields = 0;
int fieldIndex = 0; // index of the current field
@@ -3406,10 +3415,6 @@
+ " vs " + fieldIndex);
}
}
-
- // Since instances of this class are fetched via synchronous i.e. blocking
- // calls in IPCs we always recycle as soon as the instance is marshaled.
- recycle();
}
/**
@@ -3557,7 +3562,7 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mContentDescription = parcel.readCharSequence();
}
- if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 63a9990..8b64bad 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,10 @@
package android.view.autofill;
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.view.autofill.Helper.sDebug;
+import static android.view.autofill.Helper.sVerbose;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -47,13 +51,14 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityWindowInfo;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+
import org.xmlpull.v1.XmlPullParserException;
-import sun.misc.Cleaner;
import java.io.IOException;
import java.io.PrintWriter;
@@ -66,9 +71,7 @@
import java.util.List;
import java.util.Objects;
-import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
-import static android.view.autofill.Helper.sDebug;
-import static android.view.autofill.Helper.sVerbose;
+import sun.misc.Cleaner;
// TODO: use java.lang.ref.Cleaner once Android supports Java 9
@@ -616,10 +619,9 @@
/**
* @hide
*/
- public boolean isCompatibilityModeEnabled() {
- synchronized (mLock) {
- return mCompatibilityBridge != null;
- }
+ @GuardedBy("mLock")
+ public boolean isCompatibilityModeEnabledLocked() {
+ return mCompatibilityBridge != null;
}
/**
@@ -708,6 +710,7 @@
notifyViewEntered(view, 0);
}
+ @GuardedBy("mLock")
private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
if (isDisabledByServiceLocked()) {
if (sVerbose) {
@@ -748,6 +751,7 @@
}
/** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
+ @GuardedBy("mLock")
private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) {
if (shouldIgnoreViewEnteredLocked(view, flags)) return null;
@@ -791,6 +795,7 @@
}
}
+ @GuardedBy("mLock")
void notifyViewExitedLocked(@NonNull View view) {
ensureServiceClientAddedIfNeededLocked();
@@ -892,6 +897,7 @@
}
/** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
+ @GuardedBy("mLock")
private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds,
int flags) {
AutofillCallback callback = null;
@@ -935,6 +941,7 @@
}
}
+ @GuardedBy("mLock")
private void notifyViewExitedLocked(@NonNull View view, int virtualId) {
ensureServiceClientAddedIfNeededLocked();
@@ -1086,6 +1093,7 @@
}
}
+ @GuardedBy("mLock")
private void commitLocked() {
if (!mEnabled && !isActiveLocked()) {
return;
@@ -1114,6 +1122,7 @@
}
}
+ @GuardedBy("mLock")
private void cancelLocked() {
if (!mEnabled && !isActiveLocked()) {
return;
@@ -1377,11 +1386,13 @@
return new AutofillId(parent.getAutofillViewId(), virtualId);
}
+ @GuardedBy("mLock")
private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
@NonNull AutofillValue value, int flags) {
if (sVerbose) {
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
- + ", flags=" + flags + ", state=" + getStateAsStringLocked());
+ + ", flags=" + flags + ", state=" + getStateAsStringLocked()
+ + ", compatMode=" + isCompatibilityModeEnabledLocked());
}
if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
if (sVerbose) {
@@ -1392,7 +1403,7 @@
}
try {
final AutofillClient client = getClient();
- if (client == null) return; // NOTE: getClient() already logd it..
+ if (client == null) return; // NOTE: getClient() already logged it..
mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
@@ -1406,6 +1417,7 @@
}
}
+ @GuardedBy("mLock")
private void finishSessionLocked() {
if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
@@ -1420,6 +1432,7 @@
resetSessionLocked();
}
+ @GuardedBy("mLock")
private void cancelSessionLocked() {
if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
@@ -1434,6 +1447,7 @@
resetSessionLocked();
}
+ @GuardedBy("mLock")
private void resetSessionLocked() {
mSessionId = NO_SESSION;
mState = STATE_UNKNOWN;
@@ -1442,6 +1456,7 @@
mSaveTriggerId = null;
}
+ @GuardedBy("mLock")
private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
int flags) {
if (sVerbose && action != ACTION_VIEW_EXITED) {
@@ -1476,6 +1491,7 @@
}
}
+ @GuardedBy("mLock")
private void ensureServiceClientAddedIfNeededLocked() {
if (getClient() == null) {
return;
@@ -1939,11 +1955,13 @@
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
- pw.print(pfx); pw.print("compat mode enabled: "); pw.println(isCompatibilityModeEnabled());
+ pw.print(pfx); pw.print("compat mode enabled: "); pw.println(
+ isCompatibilityModeEnabledLocked());
pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
pw.print(" verbose: "); pw.println(sVerbose);
}
+ @GuardedBy("mLock")
private String getStateAsStringLocked() {
switch (mState) {
case STATE_UNKNOWN:
@@ -1961,14 +1979,17 @@
}
}
+ @GuardedBy("mLock")
private boolean isActiveLocked() {
return mState == STATE_ACTIVE;
}
+ @GuardedBy("mLock")
private boolean isDisabledByServiceLocked() {
return mState == STATE_DISABLED_BY_SERVICE;
}
+ @GuardedBy("mLock")
private boolean isFinishedLocked() {
return mState == STATE_FINISHED;
}
@@ -2164,6 +2185,7 @@
AutofillValue.forText(node.getText()));
}
+ @GuardedBy("mLock")
private void updateTrackedViewsLocked() {
if (mTrackedViews != null) {
mTrackedViews.onVisibleForAutofillChangedLocked();
@@ -2308,6 +2330,7 @@
* @param id the id of the view/virtual view whose visibility changed.
* @param isVisible visible if the view is visible in the view hierarchy.
*/
+ @GuardedBy("mLock")
void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) {
if (sDebug) {
Log.d(TAG, "notifyViewVisibilityChangedLocked(): id=" + id + " isVisible="
@@ -2341,6 +2364,7 @@
*
* @see AutofillClient#autofillClientIsVisibleForAutofill()
*/
+ @GuardedBy("mLock")
void onVisibleForAutofillChangedLocked() {
// The visibility of the views might have changed while the client was not be visible,
// hence update the visibility state for all views.
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 8edf97e..69c38ee 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -108,9 +108,9 @@
}
/**
- * Returns the language of the model.
+ * Returns a comma separated list of locales supported by the model as BCP 47 tags.
*/
- public static String getLanguage(int fd) {
+ public static String getLanguages(int fd) {
return nativeGetLanguage(fd);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9f389ba..fc03493 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -27,8 +27,10 @@
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
+import android.os.UserManager;
import android.provider.Browser;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
@@ -56,6 +58,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -99,11 +102,9 @@
private final Object mLock = new Object();
@GuardedBy("mLock") // Do not access outside this lock.
- private Map<Locale, String> mModelFilePaths;
+ private List<ModelFile> mAllModelFiles;
@GuardedBy("mLock") // Do not access outside this lock.
- private Locale mLocale;
- @GuardedBy("mLock") // Do not access outside this lock.
- private int mVersion;
+ private ModelFile mModel;
@GuardedBy("mLock") // Do not access outside this lock.
private SmartSelection mSmartSelection;
@@ -279,18 +280,18 @@
private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
synchronized (mLock) {
localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
- final Locale locale = findBestSupportedLocaleLocked(localeList);
- if (locale == null) {
- throw new FileNotFoundException("No file for null locale");
+ final ModelFile bestModel = findBestModelLocked(localeList);
+ if (bestModel == null) {
+ throw new FileNotFoundException("No model for " + localeList.toLanguageTags());
}
- if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
+ if (mSmartSelection == null || !Objects.equals(mModel, bestModel)) {
+ Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel);
destroySmartSelectionIfExistsLocked();
- final ParcelFileDescriptor fd = getFdLocked(locale);
- final int modelFd = fd.getFd();
- mVersion = SmartSelection.getVersion(modelFd);
- mSmartSelection = new SmartSelection(modelFd);
+ final ParcelFileDescriptor fd = ParcelFileDescriptor.open(
+ new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY);
+ mSmartSelection = new SmartSelection(fd.getFd());
closeAndLogError(fd);
- mLocale = locale;
+ mModel = bestModel;
}
return mSmartSelection;
}
@@ -298,74 +299,8 @@
private String getSignature(String text, int start, int end) {
synchronized (mLock) {
- return DefaultLogger.createSignature(text, start, end, mContext, mVersion, mLocale);
- }
- }
-
- @GuardedBy("mLock") // Do not call outside this lock.
- private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
- ParcelFileDescriptor updateFd;
- int updateVersion = -1;
- try {
- updateFd = ParcelFileDescriptor.open(
- new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
- if (updateFd != null) {
- updateVersion = SmartSelection.getVersion(updateFd.getFd());
- }
- } catch (FileNotFoundException e) {
- updateFd = null;
- }
- ParcelFileDescriptor factoryFd;
- int factoryVersion = -1;
- try {
- final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
- if (factoryModelFilePath != null) {
- factoryFd = ParcelFileDescriptor.open(
- new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
- if (factoryFd != null) {
- factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
- }
- } else {
- factoryFd = null;
- }
- } catch (FileNotFoundException e) {
- factoryFd = null;
- }
-
- if (updateFd == null) {
- if (factoryFd != null) {
- return factoryFd;
- } else {
- throw new FileNotFoundException(
- String.format(Locale.US, "No model file found for %s", locale));
- }
- }
-
- final int updateFdInt = updateFd.getFd();
- final boolean localeMatches = Objects.equals(
- locale.getLanguage().trim().toLowerCase(),
- SmartSelection.getLanguage(updateFdInt).trim().toLowerCase());
- if (factoryFd == null) {
- if (localeMatches) {
- return updateFd;
- } else {
- closeAndLogError(updateFd);
- throw new FileNotFoundException(
- String.format(Locale.US, "No model file found for %s", locale));
- }
- }
-
- if (!localeMatches) {
- closeAndLogError(updateFd);
- return factoryFd;
- }
-
- if (updateVersion > factoryVersion) {
- closeAndLogError(factoryFd);
- return updateFd;
- } else {
- closeAndLogError(updateFd);
- return factoryFd;
+ return DefaultLogger.createSignature(text, start, end, mContext, mModel.getVersion(),
+ mModel.getSupportedLocales());
}
}
@@ -377,60 +312,66 @@
}
}
+ /**
+ * Finds the most appropriate model to use for the given target locale list.
+ *
+ * The basic logic is: we ignore all models that don't support any of the target locales. For
+ * the remaining candidates, we take the update model unless its version number is lower than
+ * the factory version. It's assumed that factory models do not have overlapping locale ranges
+ * and conflict resolution between these models hence doesn't matter.
+ */
@GuardedBy("mLock") // Do not call outside this lock.
@Nullable
- private Locale findBestSupportedLocaleLocked(LocaleList localeList) {
+ private ModelFile findBestModelLocked(LocaleList localeList) {
// Specified localeList takes priority over the system default, so it is listed first.
final String languages = localeList.isEmpty()
? LocaleList.getDefault().toLanguageTags()
: localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags();
final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages);
- final List<Locale> supportedLocales =
- new ArrayList<>(getFactoryModelFilePathsLocked().keySet());
- final Locale updatedModelLocale = getUpdatedModelLocale();
- if (updatedModelLocale != null) {
- supportedLocales.add(updatedModelLocale);
+ ModelFile bestModel = null;
+ int bestModelVersion = -1;
+ for (ModelFile model : listAllModelsLocked()) {
+ if (model.isAnyLanguageSupported(languageRangeList)) {
+ if (model.getVersion() >= bestModelVersion) {
+ bestModel = model;
+ bestModelVersion = model.getVersion();
+ }
+ }
}
- return Locale.lookup(languageRangeList, supportedLocales);
+ return bestModel;
}
+ /** Returns a list of all model files available, in order of precedence. */
@GuardedBy("mLock") // Do not call outside this lock.
- private Map<Locale, String> getFactoryModelFilePathsLocked() {
- if (mModelFilePaths == null) {
- final Map<Locale, String> modelFilePaths = new HashMap<>();
+ private List<ModelFile> listAllModelsLocked() {
+ if (mAllModelFiles == null) {
+ final List<ModelFile> allModels = new ArrayList<>();
+ // The update model has the highest precedence.
+ if (new File(UPDATED_MODEL_FILE_PATH).exists()) {
+ final ModelFile updatedModel = ModelFile.fromPath(UPDATED_MODEL_FILE_PATH);
+ if (updatedModel != null) {
+ allModels.add(updatedModel);
+ }
+ }
+ // Factory models should never have overlapping locales, so the order doesn't matter.
final File modelsDir = new File(MODEL_DIR);
if (modelsDir.exists() && modelsDir.isDirectory()) {
- final File[] models = modelsDir.listFiles();
+ final File[] modelFiles = modelsDir.listFiles();
final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX);
- final int size = models.length;
- for (int i = 0; i < size; i++) {
- final File modelFile = models[i];
+ for (File modelFile : modelFiles) {
final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName());
if (matcher.matches() && modelFile.isFile()) {
- final String language = matcher.group(1);
- final Locale locale = Locale.forLanguageTag(language);
- modelFilePaths.put(locale, modelFile.getAbsolutePath());
+ final ModelFile model = ModelFile.fromPath(modelFile.getAbsolutePath());
+ if (model != null) {
+ allModels.add(model);
+ }
}
}
}
- mModelFilePaths = modelFilePaths;
+ mAllModelFiles = allModels;
}
- return mModelFilePaths;
- }
-
- @Nullable
- private Locale getUpdatedModelLocale() {
- try {
- final ParcelFileDescriptor updateFd = ParcelFileDescriptor.open(
- new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
- final Locale locale = Locale.forLanguageTag(
- SmartSelection.getLanguage(updateFd.getFd()));
- closeAndLogError(updateFd);
- return locale;
- } catch (FileNotFoundException e) {
- return null;
- }
+ return mAllModelFiles;
}
private TextClassification createClassificationResult(
@@ -520,6 +461,95 @@
}
/**
+ * Describes TextClassifier model files on disk.
+ */
+ private static final class ModelFile {
+
+ private final String mPath;
+ private final String mName;
+ private final int mVersion;
+ private final List<Locale> mSupportedLocales;
+
+ /** Returns null if the path did not point to a compatible model. */
+ static @Nullable ModelFile fromPath(String path) {
+ final File file = new File(path);
+ try {
+ final ParcelFileDescriptor modelFd = ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_READ_ONLY);
+ final int version = SmartSelection.getVersion(modelFd.getFd());
+ final String supportedLocalesStr = SmartSelection.getLanguages(modelFd.getFd());
+ if (supportedLocalesStr.isEmpty()) {
+ Log.d(DEFAULT_LOG_TAG, "Ignoring " + file.getAbsolutePath());
+ return null;
+ }
+ final List<Locale> supportedLocales = new ArrayList<>();
+ for (String langTag : supportedLocalesStr.split(",")) {
+ supportedLocales.add(Locale.forLanguageTag(langTag));
+ }
+ closeAndLogError(modelFd);
+ return new ModelFile(path, file.getName(), version, supportedLocales);
+ } catch (FileNotFoundException e) {
+ Log.e(DEFAULT_LOG_TAG, "Failed to peek " + file.getAbsolutePath(), e);
+ return null;
+ }
+ }
+
+ /** The absolute path to the model file. */
+ String getPath() {
+ return mPath;
+ }
+
+ /** A name to use for signature generation. Effectively the name of the model file. */
+ String getName() {
+ return mName;
+ }
+
+ /** Returns the version tag in the model's metadata. */
+ int getVersion() {
+ return mVersion;
+ }
+
+ /** Returns whether the language supports any language in the given ranges. */
+ boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) {
+ return Locale.lookup(languageRanges, mSupportedLocales) != null;
+ }
+
+ /** All locales supported by the model. */
+ List<Locale> getSupportedLocales() {
+ return Collections.unmodifiableList(mSupportedLocales);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other == null || !ModelFile.class.isAssignableFrom(other.getClass())) {
+ return false;
+ } else {
+ final ModelFile otherModel = (ModelFile) other;
+ return mPath.equals(otherModel.mPath);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner localesJoiner = new StringJoiner(",");
+ for (Locale locale : mSupportedLocales) {
+ localesJoiner.add(locale.toLanguageTag());
+ }
+ return String.format(Locale.US, "ModelFile { path=%s name=%s version=%d locales=%s }",
+ mPath, mName, mVersion, localesJoiner.toString());
+ }
+
+ private ModelFile(String path, String name, int version, List<Locale> supportedLocales) {
+ mPath = path;
+ mName = name;
+ mVersion = version;
+ mSupportedLocales = supportedLocales;
+ }
+ }
+
+ /**
* Creates intents based on the classification type.
*/
static final class IntentFactory {
@@ -541,7 +571,7 @@
case TextClassifier.TYPE_EMAIL:
return createForEmail(text);
case TextClassifier.TYPE_PHONE:
- return createForPhone(text);
+ return createForPhone(context, text);
case TextClassifier.TYPE_ADDRESS:
return createForAddress(text);
case TextClassifier.TYPE_URL:
@@ -573,15 +603,23 @@
}
@NonNull
- private static List<Intent> createForPhone(String text) {
- return Arrays.asList(
- new Intent(Intent.ACTION_DIAL)
- .setData(Uri.parse(String.format("tel:%s", text))),
- new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.PHONE, text),
- new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("smsto:%s", text))));
+ private static List<Intent> createForPhone(Context context, String text) {
+ final List<Intent> intents = new ArrayList<>();
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final Bundle userRestrictions = userManager != null
+ ? userManager.getUserRestrictions() : new Bundle();
+ if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
+ intents.add(new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text))));
+ }
+ intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+ if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
+ intents.add(new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("smsto:%s", text))));
+ }
+ return intents;
}
@NonNull
diff --git a/core/java/android/view/textclassifier/logging/DefaultLogger.java b/core/java/android/view/textclassifier/logging/DefaultLogger.java
index 6b84835..03a6d3a 100644
--- a/core/java/android/view/textclassifier/logging/DefaultLogger.java
+++ b/core/java/android/view/textclassifier/logging/DefaultLogger.java
@@ -17,7 +17,6 @@
package android.view.textclassifier.logging;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.metrics.LogMaker;
import android.util.Log;
@@ -27,8 +26,10 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
+import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* Default Logger.
@@ -210,12 +211,16 @@
*/
public static String createSignature(
String text, int start, int end, Context context, int modelVersion,
- @Nullable Locale locale) {
+ List<Locale> locales) {
Preconditions.checkNotNull(text);
Preconditions.checkNotNull(context);
- final String modelName = (locale != null)
- ? String.format(Locale.US, "%s_v%d", locale.toLanguageTag(), modelVersion)
- : "";
+ Preconditions.checkNotNull(locales);
+ final StringJoiner localesJoiner = new StringJoiner(",");
+ for (Locale locale : locales) {
+ localesJoiner.add(locale.toLanguageTag());
+ }
+ final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(),
+ modelVersion);
final int hash = Objects.hash(text, start, end, context.getPackageName());
return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash);
}
@@ -242,9 +247,9 @@
static String getModelName(String signature) {
Preconditions.checkNotNull(signature);
- final int start = signature.indexOf("|");
+ final int start = signature.indexOf("|") + 1;
final int end = signature.indexOf("|", start);
- if (start >= 0 && end >= start) {
+ if (start >= 1 && end >= start) {
return signature.substring(start, end);
}
return "";
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 5ab579d..41ceb30 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1299,6 +1299,16 @@
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.resetTouchOffsets();
}
+
+ ensureNoSelectionIfNonSelectable();
+ }
+ }
+
+ private void ensureNoSelectionIfNonSelectable() {
+ // This could be the case if a TextLink has been tapped.
+ if (!mTextView.textCanBeSelected() && mTextView.hasSelection()) {
+ Selection.setSelection((Spannable) mTextView.getText(),
+ mTextView.length(), mTextView.length());
}
}
@@ -1382,6 +1392,8 @@
// Don't leave us in the middle of a batch edit. Same as in onFocusChanged
ensureEndedBatchEdit();
+
+ ensureNoSelectionIfNonSelectable();
}
}
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 012b918..3aae849 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -904,6 +904,9 @@
}
}
+ /**
+ * @hide
+ */
@Override
protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
// Apply defaults, so as to remove UNDEFINED values
@@ -919,6 +922,9 @@
}
}
+ /**
+ * @hide
+ */
@Override
protected void onDebugDraw(Canvas canvas) {
Paint paint = new Paint();
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 7a4c800..8836561 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -18,8 +18,10 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.annotation.UiThread;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
@@ -269,4 +271,46 @@
return mWindow.getContentView().findViewById(
com.android.internal.R.id.magnifier_image);
}
+
+ /**
+ * @return the content being currently displayed in the magnifier, as bitmap
+ *
+ * @hide
+ */
+ @TestApi
+ public Bitmap getContent() {
+ return mBitmap;
+ }
+
+ /**
+ * @return the position of the magnifier window relative to the screen
+ *
+ * @hide
+ */
+ @TestApi
+ public Rect getWindowPositionOnScreen() {
+ final int[] viewLocationOnScreen = new int[2];
+ mView.getLocationOnScreen(viewLocationOnScreen);
+ final int[] viewLocationInSurface = new int[2];
+ mView.getLocationInSurface(viewLocationInSurface);
+
+ final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0];
+ final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1];
+ return new Rect(left, top, left + mWindow.getWidth(), top + mWindow.getHeight());
+ }
+
+ /**
+ * @return the size of the magnifier window in dp
+ *
+ * @hide
+ */
+ @TestApi
+ public static PointF getMagnifierDefaultSize() {
+ final Resources resources = Resources.getSystem();
+ final float density = resources.getDisplayMetrics().density;
+ final PointF size = new PointF();
+ size.x = resources.getDimension(com.android.internal.R.dimen.magnifier_width) / density;
+ size.y = resources.getDimension(com.android.internal.R.dimen.magnifier_height) / density;
+ return size;
+ }
}
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index 8f0d02f..2789bae 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -1,12 +1,9 @@
per-file TextView.java = siyamed@google.com
per-file TextView.java = nona@google.com
per-file TextView.java = clarabayarri@google.com
-per-file TextView.java = toki@google.com
per-file EditText.java = siyamed@google.com
per-file EditText.java = nona@google.com
per-file EditText.java = clarabayarri@google.com
-per-file EditText.java = toki@google.com
per-file Editor.java = siyamed@google.com
per-file Editor.java = nona@google.com
per-file Editor.java = clarabayarri@google.com
-per-file Editor.java = toki@google.com
diff --git a/core/java/android/widget/TextInputTimePickerView.java b/core/java/android/widget/TextInputTimePickerView.java
index 0cf8faa..e0261ad 100644
--- a/core/java/android/widget/TextInputTimePickerView.java
+++ b/core/java/android/widget/TextInputTimePickerView.java
@@ -174,7 +174,8 @@
*/
void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
boolean hourFormatStartsAtZero) {
- final String format = "%d";
+ final String hourFormat = "%d";
+ final String minuteFormat = "%02d";
mIs24Hour = is24Hour;
mHourFormatStartsAtZero = hourFormatStartsAtZero;
@@ -187,8 +188,8 @@
mAmPmSpinner.setSelection(1);
}
- mHourEditText.setText(String.format(format, localizedHour));
- mMinuteEditText.setText(String.format(format, minute));
+ mHourEditText.setText(String.format(hourFormat, localizedHour));
+ mMinuteEditText.setText(String.format(minuteFormat, minute));
if (mErrorShowing) {
validateInput();
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index b22ce5e..f6a69d9 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,157 +16,214 @@
package com.android.internal.app;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.annotation.Nullable;
+import android.animation.TimeAnimator;
import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
-import android.provider.Settings;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.MathUtils;
-import android.view.Gravity;
-import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
-import android.widget.ImageView;
public class PlatLogoActivity extends Activity {
- public static final boolean FINISH = true;
+ FrameLayout layout;
+ TimeAnimator anim;
+ PBackground bg;
- FrameLayout mLayout;
- int mTapCount;
- int mKeyCount;
- PathInterpolator mInterpolator = new PathInterpolator(0f, 0f, 0.5f, 1f);
+ private class PBackground extends Drawable {
+ private float maxRadius, radius, x, y, dp;
+ private int[] palette;
+ private int darkest;
+ private float offset;
+
+ public PBackground() {
+ randomizePalette();
+ }
+
+ /**
+ * set inner radius of "p" logo
+ */
+ public void setRadius(float r) {
+ this.radius = Math.max(48*dp, r);
+ }
+
+ /**
+ * move the "p"
+ */
+ public void setPosition(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * for animating the "p"
+ */
+ public void setOffset(float o) {
+ this.offset = o;
+ }
+
+ /**
+ * rough luminance calculation
+ * https://www.w3.org/TR/AERT/#color-contrast
+ */
+ public float lum(int rgb) {
+ return ((Color.red(rgb) * 299f) + (Color.green(rgb) * 587f) + (Color.blue(rgb) * 114f)) / 1000f;
+ }
+
+ /**
+ * create a random evenly-spaced color palette
+ * guaranteed to contrast!
+ */
+ public void randomizePalette() {
+ final int slots = 2 + (int)(Math.random() * 2);
+ float[] color = new float[] { (float) Math.random() * 360f, 1f, 1f };
+ palette = new int[slots];
+ darkest = 0;
+ for (int i=0; i<slots; i++) {
+ palette[i] = Color.HSVToColor(color);
+ color[0] += 360f/slots;
+ if (lum(palette[i]) < lum(palette[darkest])) darkest = i;
+ }
+
+ final StringBuilder str = new StringBuilder();
+ for (int c : palette) {
+ str.append(String.format("#%08x ", c));
+ }
+ Log.v("PlatLogoActivity", "color palette: " + str);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (dp == 0) dp = getResources().getDisplayMetrics().density;
+ final float width = canvas.getWidth();
+ final float height = canvas.getHeight();
+ if (radius == 0) {
+ setPosition(width / 2, height / 2);
+ setRadius(width / 6);
+ }
+ final float inner_w = radius * 0.667f;
+
+ final Paint paint = new Paint();
+ paint.setStrokeCap(Paint.Cap.BUTT);
+ canvas.translate(x, y);
+
+ Path p = new Path();
+ p.moveTo(-radius, height);
+ p.lineTo(-radius, 0);
+ p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
+ p.lineTo(-radius, radius);
+
+ float w = Math.max(canvas.getWidth(), canvas.getHeight()) * 1.414f;
+ paint.setStyle(Paint.Style.FILL);
+
+ int i=0;
+ while (w > radius*2 + inner_w*2) {
+ paint.setColor(0xFF000000 | palette[i % palette.length]);
+ // for a slower but more complete version:
+ // paint.setStrokeWidth(w);
+ // canvas.drawPath(p, paint);
+ canvas.drawOval(-w/2, -w/2, w/2, w/2, paint);
+ w -= inner_w * (1.1f + Math.sin((i/20f + offset) * 3.14159f));
+ i++;
+ }
+
+ // the innermost circle needs to be a constant color to avoid rapid flashing
+ paint.setColor(0xFF000000 | palette[(darkest+1) % palette.length]);
+ canvas.drawOval(-radius, -radius, radius, radius, paint);
+
+ p.reset();
+ p.moveTo(-radius, height);
+ p.lineTo(-radius, 0);
+ p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
+ p.lineTo(-radius + inner_w, radius);
+
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(inner_w*2);
+ paint.setColor(palette[darkest]);
+ canvas.drawPath(p, paint);
+ paint.setStrokeWidth(inner_w);
+ paint.setColor(0xFFFFFFFF);
+ canvas.drawPath(p, paint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mLayout = new FrameLayout(this);
- setContentView(mLayout);
+ layout = new FrameLayout(this);
+ setContentView(layout);
+
+ bg = new PBackground();
+ layout.setBackground(bg);
+
+ layout.setOnTouchListener(new View.OnTouchListener() {
+ final PointerCoords pc0 = new PointerCoords();
+ final PointerCoords pc1 = new PointerCoords();
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ if (event.getPointerCount() > 1) {
+ event.getPointerCoords(0, pc0);
+ event.getPointerCoords(1, pc1);
+ bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f);
+ }
+ break;
+ }
+ return true;
+ }
+ });
}
@Override
- public void onAttachedToWindow() {
- final DisplayMetrics dm = getResources().getDisplayMetrics();
- final float dp = dm.density;
- final int size = (int)
- (Math.min(Math.min(dm.widthPixels, dm.heightPixels), 600*dp) - 100*dp);
+ public void onStart() {
+ super.onStart();
- final ImageView im = new ImageView(this);
- final int pad = (int)(40*dp);
- im.setPadding(pad, pad, pad, pad);
- im.setTranslationZ(20);
- im.setScaleX(0.5f);
- im.setScaleY(0.5f);
- im.setAlpha(0f);
+ bg.randomizePalette();
- im.setBackground(new RippleDrawable(
- ColorStateList.valueOf(0xFF776677),
- getDrawable(com.android.internal.R.drawable.platlogo),
- null));
- im.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- final int w = view.getWidth();
- final int h = view.getHeight();
- outline.setOval((int)(w*.125), (int)(h*.125), (int)(w*.96), (int)(h*.96));
- }
- });
- im.setElevation(12f*dp);
- im.setClickable(true);
- im.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- im.setOnLongClickListener(new View.OnLongClickListener() {
+ anim = new TimeAnimator();
+ anim.setTimeListener(
+ new TimeAnimator.TimeListener() {
@Override
- public boolean onLongClick(View v) {
- if (mTapCount < 5) return false;
-
- final ContentResolver cr = getContentResolver();
- if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0)
- == 0) {
- // For posterity: the moment this user unlocked the easter egg
- try {
- Settings.System.putLong(cr,
- Settings.System.EGG_MODE,
- System.currentTimeMillis());
- } catch (RuntimeException e) {
- Log.e("PlatLogoActivity", "Can't write settings", e);
- }
- }
- im.post(new Runnable() {
- @Override
- public void run() {
- try {
- startActivity(new Intent(Intent.ACTION_MAIN)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
- .addCategory("com.android.internal.category.PLATLOGO"));
- } catch (ActivityNotFoundException ex) {
- Log.e("PlatLogoActivity", "No more eggs.");
- }
- if (FINISH) finish();
- }
- });
- return true;
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ bg.setOffset((float) totalTime / 60000f);
+ bg.invalidateSelf();
}
});
- mTapCount++;
- }
- });
- // Enable hardware keyboard input for TV compatibility.
- im.setFocusable(true);
- im.requestFocus();
- im.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode != KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
- ++mKeyCount;
- if (mKeyCount > 2) {
- if (mTapCount > 5) {
- im.performLongClick();
- } else {
- im.performClick();
- }
- }
- return true;
- } else {
- return false;
- }
- }
- });
+ anim.start();
+ }
- mLayout.addView(im, new FrameLayout.LayoutParams(size, size, Gravity.CENTER));
-
- im.animate().scaleX(1f).scaleY(1f).alpha(1f)
- .setInterpolator(mInterpolator)
- .setDuration(500)
- .setStartDelay(800)
- .start();
+ @Override
+ public void onStop() {
+ if (anim != null) {
+ anim.cancel();
+ anim = null;
+ }
+ super.onStop();
}
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 1dfff5e..6bd6930 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -106,7 +106,7 @@
int flags = PackageManager.MATCH_DEFAULT_ONLY
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
| (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
- if (intent.isBrowsableWebIntent()
+ if (intent.isWebIntent()
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
flags |= PackageManager.MATCH_INSTANT;
}
diff --git a/core/java/com/android/internal/car/ICarServiceHelper.aidl b/core/java/com/android/internal/car/ICarServiceHelper.aidl
deleted file mode 100644
index 9ee330b..0000000
--- a/core/java/com/android/internal/car/ICarServiceHelper.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.car;
-
-/**
- * Helper API for car service. Only for itneraction between system server and car service.
- * @hide
- */
-interface ICarServiceHelper {
-}
diff --git a/core/java/com/android/internal/colorextraction/types/Tonal.java b/core/java/com/android/internal/colorextraction/types/Tonal.java
index 9b7383f..7b25a06 100644
--- a/core/java/com/android/internal/colorextraction/types/Tonal.java
+++ b/core/java/com/android/internal/colorextraction/types/Tonal.java
@@ -405,12 +405,13 @@
return v - (float) Math.floor(v);
}
- static class TonalPalette {
- final float[] h;
- final float[] s;
- final float[] l;
- final float minHue;
- final float maxHue;
+ @VisibleForTesting
+ public static class TonalPalette {
+ public final float[] h;
+ public final float[] s;
+ public final float[] l;
+ public final float minHue;
+ public final float maxHue;
TonalPalette(float[] h, float[] s, float[] l) {
if (h.length != s.length || s.length != l.length) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 40dcf25b..eb58b09 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -678,7 +678,8 @@
StopwatchTimer mCameraOnTimer;
int mGpsSignalQualityBin = -1;
- final StopwatchTimer[] mGpsSignalQualityTimer =
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected final StopwatchTimer[] mGpsSignalQualityTimer =
new StopwatchTimer[GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS];
int mPhoneSignalStrengthBin = -1;
@@ -760,6 +761,8 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected StopwatchTimer mBluetoothScanTimer;
+ boolean mIsCellularTxPowerHigh = false;
+
int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
long mMobileRadioActiveStartTime;
StopwatchTimer mMobileRadioActiveTimer;
@@ -3522,7 +3525,7 @@
mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
}
- void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ void addHistoryBufferLocked(long elapsedRealtimeMs, HistoryItem cur) {
if (!mHaveBatteryLevel || !mRecordingHistory) {
return;
}
@@ -3603,8 +3606,8 @@
} else if (dataSize >= MAX_HISTORY_BUFFER) {
if (!mHistoryOverflow) {
mHistoryOverflow = true;
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur);
return;
}
@@ -3642,7 +3645,7 @@
return;
}
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
return;
}
@@ -3650,15 +3653,14 @@
// The history is currently empty; we need it to start with a time stamp.
cur.currentTime = System.currentTimeMillis();
if (recordResetDueToOverflow) {
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur);
}
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur);
}
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
}
- private void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd,
- HistoryItem cur) {
+ private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) {
if (mIteratingHistory) {
throw new IllegalStateException("Can't do this while iterating history!");
}
@@ -3692,17 +3694,17 @@
mHistoryAddTmp.wakeReasonTag = null;
mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
- addHistoryRecordInnerLocked(wakeElapsedTime, uptimeMs, mHistoryAddTmp);
+ addHistoryRecordInnerLocked(wakeElapsedTime, mHistoryAddTmp);
}
}
mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
mTrackRunningHistoryElapsedRealtime = elapsedRealtimeMs;
mTrackRunningHistoryUptime = uptimeMs;
- addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+ addHistoryRecordInnerLocked(elapsedRealtimeMs, mHistoryCur);
}
- void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
+ void addHistoryRecordInnerLocked(long elapsedRealtimeMs, HistoryItem cur) {
+ addHistoryBufferLocked(elapsedRealtimeMs, cur);
if (!USE_OLD_HISTORY) {
return;
@@ -3743,7 +3745,7 @@
if (mNumHistoryItems == MAX_HISTORY_ITEMS
|| mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) {
- addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur);
}
if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
@@ -3760,7 +3762,7 @@
}
}
- addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
}
public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
@@ -3826,6 +3828,7 @@
mActiveHistoryStates2 = 0xffffffff;
}
+ @GuardedBy("this")
public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
long realtime) {
final boolean screenOff = !isScreenOn(screenState);
@@ -3903,6 +3906,7 @@
* This should only be called after the cpu times have been read.
* @see #scheduleRemoveIsolatedUidLocked(int, int)
*/
+ @GuardedBy("this")
public void removeIsolatedUidLocked(int isolatedUid) {
StatsLog.write(
StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1),
@@ -4732,6 +4736,7 @@
return;
}
+ @GuardedBy("this")
public void noteScreenStateLocked(int state) {
state = mPretendScreenOff ? Display.STATE_OFF : state;
@@ -9648,6 +9653,7 @@
return ps;
}
+ @GuardedBy("mBsi")
public void updateUidProcessStateLocked(int procState) {
int uidRunningState;
// Make special note of Foreground Services
@@ -9931,8 +9937,6 @@
if (wl != null) {
StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
wlt.stopRunningLocked(elapsedRealtimeMs);
- if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
- }
}
if (type == WAKE_TYPE_PARTIAL) {
if (mAggregatedPartialWakelockTimer != null) {
@@ -11214,6 +11218,9 @@
Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
}
+ // Add modem tx power to history.
+ addModemTxPowerToHistory(activityInfo);
+
// Grab a separate lock to acquire the network stats, which may do I/O.
NetworkStats delta = null;
synchronized (mModemNetworkLock) {
@@ -11388,6 +11395,44 @@
new BluetoothActivityEnergyInfo(0, 0, 0, 0, 0, 0);
/**
+ * Add modem tx power to history
+ * Device is said to be in high cellular transmit power when it has spent most of the transmit
+ * time at the highest power level.
+ * @param activityInfo
+ */
+ private void addModemTxPowerToHistory(final ModemActivityInfo activityInfo) {
+ if (activityInfo == null) {
+ return;
+ }
+ int[] txTimeMs = activityInfo.getTxTimeMillis();
+ if (txTimeMs == null || txTimeMs.length != ModemActivityInfo.TX_POWER_LEVELS) {
+ return;
+ }
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
+ int levelMaxTimeSpent = 0;
+ for (int i = 1; i < txTimeMs.length; i++) {
+ if (txTimeMs[i] > txTimeMs[levelMaxTimeSpent]) {
+ levelMaxTimeSpent = i;
+ }
+ }
+ if (levelMaxTimeSpent == ModemActivityInfo.TX_POWER_LEVELS - 1) {
+ if (!mIsCellularTxPowerHigh) {
+ mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ mIsCellularTxPowerHigh = true;
+ }
+ return;
+ }
+ if (mIsCellularTxPowerHigh) {
+ mHistoryCur.states2 &= ~HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ mIsCellularTxPowerHigh = false;
+ }
+ return;
+ }
+
+ /**
* Distribute Bluetooth energy info and network traffic to apps.
* @param info The energy information from the bluetooth controller.
*/
@@ -11721,6 +11766,7 @@
* and we are on battery with screen off, we give more of the cpu time to those apps holding
* wakelocks. If the screen is on, we just assign the actual cpu time an app used.
*/
+ @GuardedBy("this")
public void updateCpuTimeLocked() {
if (mPowerProfile == null) {
return;
@@ -12166,6 +12212,7 @@
return false;
}
+ @GuardedBy("this")
protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
boolean doWrite = false;
@@ -12307,7 +12354,7 @@
boolean reset) {
mRecordingHistory = true;
mHistoryCur.currentTime = System.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs,
+ addHistoryBufferLocked(elapsedRealtimeMs,
reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
mHistoryCur);
mHistoryCur.currentTime = 0;
@@ -12320,8 +12367,7 @@
final long uptimeMs) {
if (mRecordingHistory) {
mHistoryCur.currentTime = currentTime;
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME,
- mHistoryCur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur);
mHistoryCur.currentTime = 0;
}
}
@@ -12329,8 +12375,7 @@
private void recordShutdownLocked(final long elapsedRealtimeMs, final long uptimeMs) {
if (mRecordingHistory) {
mHistoryCur.currentTime = System.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_SHUTDOWN,
- mHistoryCur);
+ addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur);
mHistoryCur.currentTime = 0;
}
}
@@ -12344,6 +12389,7 @@
// This should probably be exposed in the API, though it's not critical
public static final int BATTERY_PLUGGED_NONE = OsProtoEnums.BATTERY_PLUGGED_NONE; // = 0
+ @GuardedBy("this")
public void setBatteryStateLocked(final int status, final int health, final int plugType,
final int level, /* not final */ int temp, final int volt, final int chargeUAh,
final int chargeFullUAh) {
@@ -13159,6 +13205,7 @@
}
}
+ @GuardedBy("this")
public void dumpConstantsLocked(PrintWriter pw) {
mConstants.dumpLocked(pw);
}
@@ -13274,7 +13321,7 @@
if (USE_OLD_HISTORY) {
addHistoryRecordLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
}
- addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
+ addHistoryBufferLocked(elapsedRealtime, HistoryItem.CMD_START, mHistoryCur);
startRecordingHistory(elapsedRealtime, uptime, false);
}
@@ -13542,6 +13589,7 @@
mCameraOnTimer.readSummaryFromParcelLocked(in);
mBluetoothScanNesting = 0;
mBluetoothScanTimer.readSummaryFromParcelLocked(in);
+ mIsCellularTxPowerHigh = false;
int NRPMS = in.readInt();
if (NRPMS > 10000) {
@@ -14478,6 +14526,7 @@
mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase, in);
mBluetoothScanNesting = 0;
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase, in);
+ mIsCellularTxPowerHigh = false;
mDischargeUnplugLevel = in.readInt();
mDischargePlugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 088e726..12405eb 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -283,6 +283,7 @@
return -OsConstants.EBADF;
}
+ @GuardedBy("mLock")
private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
if (entry == null) {
@@ -291,12 +292,14 @@
return entry;
}
+ @GuardedBy("mLock")
private void recycleLocked(Args args) {
if (mArgsPool.size() < ARGS_POOL_SIZE) {
mArgsPool.add(args);
}
}
+ @GuardedBy("mLock")
private void replySimpleLocked(long unique, int result) {
if (mInstance != 0) {
native_replySimple(mInstance, unique, result);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f70d3c2..221bf88 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -35,7 +35,7 @@
void animateCollapsePanels();
void togglePanel();
- void showChargingAnimation(int batteryLevel);
+ void showWirelessChargingAnimation(int batteryLevel);
/**
* Notifies the status bar of a System UI visibility flag change.
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 74dbaba..f05bd32 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -139,6 +139,10 @@
* @see com.android.server.wm.WindowManagerService#inputMethodClientHasFocus(IInputMethodClient)
*/
int ERROR_NOT_IME_TARGET_WINDOW = 11;
+ /**
+ * Indicates that focused view in the current window is not an editor.
+ */
+ int ERROR_NO_EDITOR = 12;
}
@ResultCode
@@ -258,6 +262,8 @@
return "ERROR_NULL";
case ResultCode.ERROR_NO_IME:
return "ERROR_NO_IME";
+ case ResultCode.ERROR_NO_EDITOR:
+ return "ERROR_NO_EDITOR";
case ResultCode.ERROR_INVALID_PACKAGE_NAME:
return "ERROR_INVALID_PACKAGE_NAME";
case ResultCode.ERROR_SYSTEM_NOT_READY:
@@ -288,6 +294,10 @@
*/
public static final InputBindResult NO_IME = error(ResultCode.ERROR_NO_IME);
/**
+ * Predefined error object for {@link ResultCode#NO_EDITOR}.
+ */
+ public static final InputBindResult NO_EDITOR = error(ResultCode.ERROR_NO_EDITOR);
+ /**
* Predefined error object for {@link ResultCode#ERROR_INVALID_PACKAGE_NAME}.
*/
public static final InputBindResult INVALID_PACKAGE_NAME =
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index b53459e..67dc81a 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -551,6 +551,7 @@
mPreventDispatchingItemsChanged = true;
clear();
clearHeader();
+ mPresenters.clear();
mPreventDispatchingItemsChanged = false;
mItemsChangedWhileDispatchPrevented = false;
onItemsChanged(true);
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
new file mode 100644
index 0000000..71d3bb5
--- /dev/null
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A class to extract Bitmaps from a MessagingStyle message.
+ */
+public class LocalImageResolver {
+
+ private static final int MAX_SAFE_ICON_SIZE_PX = 480;
+
+ @Nullable
+ public static Drawable resolveImage(Uri uri, Context context) throws IOException {
+ BitmapFactory.Options onlyBoundsOptions = getBoundsOptionsForImage(uri, context);
+ if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
+ return null;
+ }
+
+ int originalSize =
+ (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth)
+ ? onlyBoundsOptions.outHeight
+ : onlyBoundsOptions.outWidth;
+
+ double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX)
+ ? (originalSize / MAX_SAFE_ICON_SIZE_PX)
+ : 1.0;
+
+ BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
+ bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
+ InputStream input = context.getContentResolver().openInputStream(uri);
+ Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
+ input.close();
+ return new BitmapDrawable(context.getResources(), bitmap);
+ }
+
+ private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context)
+ throws IOException {
+ InputStream input = context.getContentResolver().openInputStream(uri);
+ BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
+ onlyBoundsOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
+ input.close();
+ return onlyBoundsOptions;
+ }
+
+ private static int getPowerOfTwoForSampleRatio(double ratio) {
+ int k = Integer.highestOneBit((int) Math.floor(ratio));
+ return Math.max(1, k);
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 5577d6e..239beaa 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -22,9 +22,12 @@
import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.View;
@@ -61,6 +64,11 @@
private boolean mIsHidingAnimated;
private boolean mNeedsGeneratedAvatar;
private Notification.Person mSender;
+ private boolean mAvatarsAtEnd;
+ private ViewGroup mImageContainer;
+ private MessagingImageMessage mIsolatedMessage;
+ private boolean mTransformingImages;
+ private Point mDisplaySize = new Point();
public MessagingGroup(@NonNull Context context) {
super(context);
@@ -87,6 +95,35 @@
mSenderName = findViewById(R.id.message_name);
mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
mAvatarView = findViewById(R.id.message_icon);
+ mImageContainer = findViewById(R.id.messaging_group_icon_container);
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ mDisplaySize.x = displayMetrics.widthPixels;
+ mDisplaySize.y = displayMetrics.heightPixels;
+ }
+
+ public void updateClipRect() {
+ // We want to clip to the senderName if it's available, otherwise our images will come
+ // from a weird position
+ Rect clipRect;
+ if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) {
+ ViewGroup parent = (ViewGroup) mSenderName.getParent();
+ int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent(
+ mMessageContainer, parent) + mSenderName.getHeight();
+ clipRect = new Rect(0, top, mDisplaySize.x, mDisplaySize.y);
+ } else {
+ clipRect = null;
+ }
+ mMessageContainer.setClipBounds(clipRect);
+ }
+
+ private int getDistanceFromParent(View searchedView, ViewGroup parent) {
+ int position = 0;
+ View view = searchedView;
+ while(view != parent) {
+ position += view.getTop() + view.getTranslationY();
+ view = (View) view.getParent();
+ }
+ return position;
}
public void setSender(Notification.Person sender, CharSequence nameOverride) {
@@ -129,12 +166,14 @@
}
public void removeMessage(MessagingMessage messagingMessage) {
- mMessageContainer.removeView(messagingMessage);
+ ViewGroup messageParent = (ViewGroup) messagingMessage.getView().getParent();
+ messageParent.removeView(messagingMessage.getView());
Runnable recycleRunnable = () -> {
- mMessageContainer.removeTransientView(messagingMessage);
+ messageParent.removeTransientView(messagingMessage.getView());
messagingMessage.recycle();
if (mMessageContainer.getChildCount() == 0
- && mMessageContainer.getTransientViewCount() == 0) {
+ && mMessageContainer.getTransientViewCount() == 0
+ && mImageContainer.getChildCount() == 0) {
ViewParent parent = getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(MessagingGroup.this);
@@ -148,9 +187,10 @@
}
};
if (isShown()) {
- mMessageContainer.addTransientView(messagingMessage, 0);
- performRemoveAnimation(messagingMessage, recycleRunnable);
- if (mMessageContainer.getChildCount() == 0) {
+ messageParent.addTransientView(messagingMessage.getView(), 0);
+ performRemoveAnimation(messagingMessage.getView(), recycleRunnable);
+ if (mMessageContainer.getChildCount() == 0
+ && mImageContainer.getChildCount() == 0) {
removeGroupAnimated(null);
}
} else {
@@ -160,12 +200,8 @@
}
private void removeGroupAnimated(Runnable endAction) {
- MessagingPropertyAnimator.fadeOut(mAvatarView, null);
- MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
- (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
- MessagingPropertyAnimator.fadeOut(mSenderName, null);
- MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
- (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ performRemoveAnimation(mAvatarView, null);
+ performRemoveAnimation(mSenderName, null);
boolean endActionTriggered = false;
for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
View child = mMessageContainer.getChildAt(i);
@@ -182,14 +218,17 @@
performRemoveAnimation(child, childEndAction);
endActionTriggered = true;
}
+ if (mIsolatedMessage != null) {
+ performRemoveAnimation(mIsolatedMessage, !endActionTriggered ? endAction : null);
+ endActionTriggered = true;
+ }
if (!endActionTriggered && endAction != null) {
endAction.run();
}
}
- public void performRemoveAnimation(View message,
- Runnable recycleRunnable) {
- MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+ public void performRemoveAnimation(View message, Runnable endAction) {
+ MessagingPropertyAnimator.fadeOut(message, endAction);
MessagingPropertyAnimator.startLocalTranslationTo(message,
(int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
}
@@ -222,6 +261,9 @@
}
}
}
+ if (mMessageContainer.getChildCount() == 0 && mIsolatedMessage != null) {
+ return mIsolatedMessage.getMeasuredType();
+ }
return MEASURED_NORMAL;
}
@@ -234,6 +276,7 @@
result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
}
}
+ result = mIsolatedMessage != null ? Math.max(result, 1) : result;
// A group is usually taking up quite some space with the padding and the name, let's add 1
return result + 1;
}
@@ -289,26 +332,67 @@
public void setMessages(List<MessagingMessage> group) {
// Let's now make sure all children are added and in the correct order
+ int textMessageIndex = 0;
+ MessagingImageMessage isolatedMessage = null;
for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
MessagingMessage message = group.get(messageIndex);
+ message.setColor(mTextColor);
if (message.getGroup() != this) {
message.setMessagingGroup(this);
- ViewParent parent = mMessageContainer.getParent();
- if (parent instanceof ViewGroup) {
- ((ViewGroup) parent).removeView(message);
- }
- mMessageContainer.addView(message, messageIndex);
mAddedMessages.add(message);
}
- if (messageIndex != mMessageContainer.indexOfChild(message)) {
- mMessageContainer.removeView(message);
- mMessageContainer.addView(message, messageIndex);
+ boolean isImage = message instanceof MessagingImageMessage;
+ if (mAvatarsAtEnd && isImage) {
+ isolatedMessage = (MessagingImageMessage) message;
+ } else {
+ if (removeFromParentIfDifferent(message, mMessageContainer)) {
+ ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams();
+ if (layoutParams != null
+ && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) {
+ message.getView().setLayoutParams(
+ mMessageContainer.generateDefaultLayoutParams());
+ }
+ mMessageContainer.addView(message.getView(), textMessageIndex);
+ }
+ if (isImage) {
+ ((MessagingImageMessage) message).setIsolated(false);
+ }
+ // Let's sort them properly
+ if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) {
+ mMessageContainer.removeView(message.getView());
+ mMessageContainer.addView(message.getView(), textMessageIndex);
+ }
+ textMessageIndex++;
}
- message.setTextColor(mTextColor);
}
+ if (isolatedMessage != null) {
+ if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) {
+ mImageContainer.removeAllViews();
+ mImageContainer.addView(isolatedMessage.getView());
+ }
+ isolatedMessage.setIsolated(true);
+ } else if (mIsolatedMessage != null) {
+ mImageContainer.removeAllViews();
+ }
+ mIsolatedMessage = isolatedMessage;
mMessages = group;
}
+ /**
+ * Remove the message from the parent if the parent isn't the one provided
+ * @return whether the message was removed
+ */
+ private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) {
+ ViewParent parent = message.getView().getParent();
+ if (parent != newParent) {
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(message.getView());
+ }
+ return true;
+ }
+ return false;
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -317,13 +401,14 @@
@Override
public boolean onPreDraw() {
for (MessagingMessage message : mAddedMessages) {
- if (!message.isShown()) {
+ if (!message.getView().isShown()) {
continue;
}
- MessagingPropertyAnimator.fadeIn(message);
+ MessagingPropertyAnimator.fadeIn(message.getView());
if (!mFirstLayout) {
- MessagingPropertyAnimator.startLocalTranslationFrom(message,
- message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+ MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(),
+ message.getView().getHeight(),
+ MessagingLayout.LINEAR_OUT_SLOW_IN);
}
}
mAddedMessages.clear();
@@ -333,6 +418,7 @@
});
}
mFirstLayout = false;
+ updateClipRect();
}
/**
@@ -372,6 +458,10 @@
return mMessageContainer;
}
+ public MessagingImageMessage getIsolatedMessage() {
+ return mIsolatedMessage;
+ }
+
public boolean needsGeneratedAvatar() {
return mNeedsGeneratedAvatar;
}
@@ -379,4 +469,19 @@
public Notification.Person getSender() {
return mSender;
}
+
+ public void setTransformingImages(boolean transformingImages) {
+ mTransformingImages = transformingImages;
+ }
+
+ public void setDisplayAvatarsAtEnd(boolean atEnd) {
+ if (mAvatarsAtEnd != atEnd) {
+ mAvatarsAtEnd = atEnd;
+ mImageContainer.setVisibility(atEnd ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ public List<MessagingMessage> getMessages() {
+ return mMessages;
+ }
}
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
new file mode 100644
index 0000000..961f90a
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.io.IOException;
+
+/**
+ * A message of a {@link MessagingLayout} that is an image.
+ */
+@RemoteViews.RemoteView
+public class MessagingImageMessage extends ImageView implements MessagingMessage {
+ private static final String TAG = "MessagingImageMessage";
+ private static Pools.SimplePool<MessagingImageMessage> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private final MessagingMessageState mState = new MessagingMessageState(this);
+ private final int mMinImageHeight;
+ private final Path mPath = new Path();
+ private final int mImageRounding;
+ private final int mMaxImageHeight;
+ private final int mIsolatedSize;
+ private final int mExtraSpacing;
+ private Drawable mDrawable;
+ private float mAspectRatio;
+ private int mActualWidth;
+ private int mActualHeight;
+ private boolean mIsIsolated;
+
+ public MessagingImageMessage(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mMinImageHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.messaging_image_min_size);
+ mMaxImageHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.messaging_image_max_height);
+ mImageRounding = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.messaging_image_rounding);
+ mExtraSpacing = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.messaging_image_extra_spacing);
+ setMaxHeight(mMaxImageHeight);
+ mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ }
+
+ @Override
+ public MessagingMessageState getState() {
+ return mState;
+ }
+
+ @Override
+ public boolean setMessage(Notification.MessagingStyle.Message message) {
+ MessagingMessage.super.setMessage(message);
+ Drawable drawable;
+ try {
+ drawable = LocalImageResolver.resolveImage(message.getDataUri(), getContext());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ int intrinsicHeight = drawable.getIntrinsicHeight();
+ if (intrinsicHeight == 0) {
+ Log.w(TAG, "Drawable with 0 intrinsic height was returned");
+ return false;
+ }
+ mDrawable = drawable;
+ mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
+ setImageDrawable(drawable);
+ setContentDescription(message.getText());
+ return true;
+ }
+
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+ MessagingImageMessage createdMessage = sInstancePool.acquire();
+ if (createdMessage == null) {
+ createdMessage = (MessagingImageMessage) LayoutInflater.from(
+ layout.getContext()).inflate(
+ R.layout.notification_template_messaging_image_message,
+ messagingLinearLayout,
+ false);
+ createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ }
+ boolean created = createdMessage.setMessage(m);
+ if (!created) {
+ createdMessage.recycle();
+ return MessagingTextMessage.createMessage(layout, m);
+ }
+ return createdMessage;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.save();
+ canvas.clipPath(getRoundedRectPath());
+ int width = (int) Math.max(getActualWidth(), getActualHeight() * mAspectRatio);
+ int height = (int) (width / mAspectRatio);
+ int left = (int) ((getActualWidth() - width) / 2.0f);
+ mDrawable.setBounds(left, 0, left + width, height);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ }
+
+ public Path getRoundedRectPath() {
+ int left = 0;
+ int right = getActualWidth();
+ int top = 0;
+ int bottom = getActualHeight();
+ mPath.reset();
+ int width = right - left;
+ float roundnessX = mImageRounding;
+ float roundnessY = mImageRounding;
+ roundnessX = Math.min(width / 2, roundnessX);
+ roundnessY = Math.min((bottom - top) / 2, roundnessY);
+ mPath.moveTo(left, top + roundnessY);
+ mPath.quadTo(left, top, left + roundnessX, top);
+ mPath.lineTo(right - roundnessX, top);
+ mPath.quadTo(right, top, right, top + roundnessY);
+ mPath.lineTo(right, bottom - roundnessY);
+ mPath.quadTo(right, bottom, right - roundnessX, bottom);
+ mPath.lineTo(left + roundnessX, bottom);
+ mPath.quadTo(left, bottom, left, bottom - roundnessY);
+ mPath.close();
+ return mPath;
+ }
+
+ public void recycle() {
+ MessagingMessage.super.recycle();
+ setAlpha(1.0f);
+ setTranslationY(0);
+ setImageBitmap(null);
+ mDrawable = null;
+ sInstancePool.release(this);
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ @Override
+ public int getMeasuredType() {
+ int measuredHeight = getMeasuredHeight();
+ int minImageHeight;
+ if (mIsIsolated) {
+ minImageHeight = mIsolatedSize;
+ } else {
+ minImageHeight = mMinImageHeight;
+ }
+ boolean measuredTooSmall = measuredHeight < minImageHeight
+ && measuredHeight != mDrawable.getIntrinsicHeight();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ if (!mIsIsolated && measuredHeight != mDrawable.getIntrinsicHeight()) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_NORMAL;
+ }
+ }
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ // Nothing to do, this should be handled automatically.
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mIsIsolated) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec));
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // TODO: ensure that this isn't called when transforming
+ setActualWidth(getStaticWidth());
+ setActualHeight(getHeight());
+ }
+
+ @Override
+ public int getConsumedLines() {
+ return 3;
+ }
+
+ public void setActualWidth(int actualWidth) {
+ mActualWidth = actualWidth;
+ invalidate();
+ }
+
+ public int getActualWidth() {
+ return mActualWidth;
+ }
+
+ public void setActualHeight(int actualHeight) {
+ mActualHeight = actualHeight;
+ invalidate();
+ }
+
+ public int getActualHeight() {
+ return mActualHeight;
+ }
+
+ public int getStaticWidth() {
+ if (mIsIsolated) {
+ return getWidth();
+ }
+ return (int) (getHeight() * mAspectRatio);
+ }
+
+ public void setIsolated(boolean isolated) {
+ if (mIsIsolated != isolated) {
+ mIsIsolated = isolated;
+ // update the layout params not to have margins
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) getLayoutParams();
+ layoutParams.topMargin = isolated ? 0 : mExtraSpacing;
+ setLayoutParams(layoutParams);
+ }
+ }
+
+ @Override
+ public int getExtraSpacing() {
+ return mExtraSpacing;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index d45c086..5279636 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -81,6 +81,7 @@
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
private Notification.Person mUser;
private CharSequence mNameReplacement;
+ private boolean mIsCollapsed;
public MessagingLayout(@NonNull Context context) {
super(context);
@@ -127,6 +128,11 @@
}
@RemotableViewMethod
+ public void setIsCollapsed(boolean isCollapsed) {
+ mIsCollapsed = isCollapsed;
+ }
+
+ @RemotableViewMethod
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -331,6 +337,7 @@
newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
mAddedGroups.add(newGroup);
}
+ newGroup.setDisplayAvatarsAtEnd(mIsCollapsed);
newGroup.setLayoutColor(mLayoutColor);
Notification.Person sender = senders.get(groupIndex);
CharSequence nameOverride = null;
@@ -392,7 +399,6 @@
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
message = MessagingMessage.createMessage(this, m);
- message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
}
message.setIsHistoric(historic);
result.add(message);
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index f0ef370..991e3e7 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -75,7 +75,6 @@
targetHeight = Integer.MAX_VALUE;
break;
}
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
// Now that we know which views to take, fix up the indents and see what width we get.
int measuredWidth = mPaddingLeft + mPaddingRight;
@@ -90,7 +89,6 @@
totalHeight = mPaddingTop + mPaddingBottom;
boolean first = true;
int linesRemaining = mMaxDisplayedLines;
-
// Starting from the bottom: we measure every view as if it were the only one. If it still
// fits, we take it, otherwise we stop there.
for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
@@ -100,11 +98,13 @@
final View child = getChildAt(i);
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
MessagingChild messagingChild = null;
+ int spacing = mSpacing;
if (child instanceof MessagingChild) {
messagingChild = (MessagingChild) child;
messagingChild.setMaxDisplayedLines(linesRemaining);
+ spacing += messagingChild.getExtraSpacing();
}
- int spacing = first ? 0 : mSpacing;
+ spacing = first ? 0 : spacing;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
- mPaddingTop - mPaddingBottom + spacing);
@@ -254,6 +254,9 @@
void setMaxDisplayedLines(int lines);
void hideAnimated();
boolean isHidingAnimated();
+ default int getExtraSpacing() {
+ return 0;
+ }
}
public static class LayoutParams extends MarginLayoutParams {
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index f09621f..bf1c5ca 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -16,182 +16,125 @@
package com.android.internal.widget;
-import android.annotation.AttrRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StyleRes;
import android.app.Notification;
-import android.content.Context;
-import android.text.Layout;
-import android.util.AttributeSet;
-import android.util.Pools;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.RemoteViews;
-
-import com.android.internal.R;
+import android.view.View;
import java.util.Objects;
/**
* A message of a {@link MessagingLayout}.
*/
-@RemoteViews.RemoteView
-public class MessagingMessage extends ImageFloatingTextView implements
- MessagingLinearLayout.MessagingChild {
+public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
- private static Pools.SimplePool<MessagingMessage> sInstancePool
- = new Pools.SynchronizedPool<>(10);
- private Notification.MessagingStyle.Message mMessage;
- private MessagingGroup mGroup;
- private boolean mIsHistoric;
- private boolean mIsHidingAnimated;
+ /**
+ * Prefix for supported image MIME types
+ **/
+ String IMAGE_MIME_TYPE_PREFIX = "image/";
- public MessagingMessage(@NonNull Context context) {
- super(context);
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ if (hasImage(m)) {
+ return MessagingImageMessage.createMessage(layout, m);
+ } else {
+ return MessagingTextMessage.createMessage(layout, m);
+ }
}
- public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
+ static void dropCache() {
+ MessagingTextMessage.dropCache();
+ MessagingImageMessage.dropCache();
}
- public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ static boolean hasImage(Notification.MessagingStyle.Message m) {
+ return m.getDataUri() != null
+ && m.getDataMimeType() != null
+ && m.getDataMimeType().startsWith(IMAGE_MIME_TYPE_PREFIX);
}
- public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
+ /**
+ * Set a message for this view.
+ * @return true if setting the message worked
+ */
+ default boolean setMessage(Notification.MessagingStyle.Message message) {
+ getState().setMessage(message);
+ return true;
}
- private void setMessage(Notification.MessagingStyle.Message message) {
- mMessage = message;
- setText(message.getText());
+ default Notification.MessagingStyle.Message getMessage() {
+ return getState().getMessage();
}
- public Notification.MessagingStyle.Message getMessage() {
- return mMessage;
- }
-
- boolean sameAs(Notification.MessagingStyle.Message message) {
- if (!Objects.equals(message.getText(), mMessage.getText())) {
+ default boolean sameAs(Notification.MessagingStyle.Message message) {
+ Notification.MessagingStyle.Message ownMessage = getMessage();
+ if (!Objects.equals(message.getText(), ownMessage.getText())) {
return false;
}
- if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+ if (!Objects.equals(message.getSender(), ownMessage.getSender())) {
return false;
}
- if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+ if (!Objects.equals(message.getTimestamp(), ownMessage.getTimestamp())) {
+ return false;
+ }
+ if (!Objects.equals(message.getDataMimeType(), ownMessage.getDataMimeType())) {
+ return false;
+ }
+ if (!Objects.equals(message.getDataUri(), ownMessage.getDataUri())) {
return false;
}
return true;
}
- boolean sameAs(MessagingMessage message) {
+ default boolean sameAs(MessagingMessage message) {
return sameAs(message.getMessage());
}
- static MessagingMessage createMessage(MessagingLayout layout,
- Notification.MessagingStyle.Message m) {
- MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
- MessagingMessage createdMessage = sInstancePool.acquire();
- if (createdMessage == null) {
- createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
- R.layout.notification_template_messaging_message, messagingLinearLayout,
- false);
- }
- createdMessage.setMessage(m);
- return createdMessage;
+ default void removeMessage() {
+ getGroup().removeMessage(this);
}
- public void removeMessage() {
- mGroup.removeMessage(this);
+ default void setMessagingGroup(MessagingGroup group) {
+ getState().setGroup(group);
}
- public void recycle() {
- mGroup = null;
- mMessage = null;
- setAlpha(1.0f);
- setTranslationY(0);
- sInstancePool.release(this);
+ default void setIsHistoric(boolean isHistoric) {
+ getState().setIsHistoric(isHistoric);
}
- public void setMessagingGroup(MessagingGroup group) {
- mGroup = group;
+ default MessagingGroup getGroup() {
+ return getState().getGroup();
}
- public static void dropCache() {
- sInstancePool = new Pools.SynchronizedPool<>(10);
- }
-
- public void setIsHistoric(boolean isHistoric) {
- mIsHistoric = isHistoric;
- }
-
- public MessagingGroup getGroup() {
- return mGroup;
+ default void setIsHidingAnimated(boolean isHiding) {
+ getState().setIsHidingAnimated(isHiding);
}
@Override
- public int getMeasuredType() {
- boolean measuredTooSmall = getMeasuredHeight()
- < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
- if (measuredTooSmall) {
- return MEASURED_TOO_SMALL;
- } else {
- Layout layout = getLayout();
- if (layout == null) {
- return MEASURED_TOO_SMALL;
- }
- if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
- return MEASURED_SHORTENED;
- } else {
- return MEASURED_NORMAL;
- }
- }
+ default boolean isHidingAnimated() {
+ return getState().isHidingAnimated();
}
@Override
- public void hideAnimated() {
+ default void hideAnimated() {
setIsHidingAnimated(true);
- mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+ getGroup().performRemoveAnimation(getState().getHostView(),
+ () -> setIsHidingAnimated(false));
}
- private void setIsHidingAnimated(boolean isHiding) {
- ViewParent parent = getParent();
- mIsHidingAnimated = isHiding;
- invalidate();
- if (parent instanceof ViewGroup) {
- ((ViewGroup) parent).invalidate();
- }
- }
-
- @Override
- public boolean isHidingAnimated() {
- return mIsHidingAnimated;
- }
-
- @Override
- public void setMaxDisplayedLines(int lines) {
- setMaxLines(lines);
- }
-
- @Override
- public int getConsumedLines() {
- return getLineCount();
- }
-
- public int getLayoutHeight() {
- Layout layout = getLayout();
- if (layout == null) {
- return 0;
- }
- return layout.getHeight();
- }
-
- @Override
- public boolean hasOverlappingRendering() {
+ default boolean hasOverlappingRendering() {
return false;
}
+
+ default void recycle() {
+ getState().reset();
+ }
+
+ default View getView() {
+ return (View) this;
+ }
+
+ default void setColor(int textColor) {}
+
+ MessagingMessageState getState();
+
+ void setVisibility(int visibility);
}
diff --git a/core/java/com/android/internal/widget/MessagingMessageState.java b/core/java/com/android/internal/widget/MessagingMessageState.java
new file mode 100644
index 0000000..ac62472
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessageState.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.app.Notification;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * Shared state and implementation for MessagingMessages. Used to share common implementations.
+ */
+public class MessagingMessageState {
+ private final View mHostView;
+ private Notification.MessagingStyle.Message mMessage;
+ private MessagingGroup mGroup;
+ private boolean mIsHistoric;
+ private boolean mIsHidingAnimated;
+
+ MessagingMessageState(View hostView) {
+ mHostView = hostView;
+ }
+
+ public void setMessage(Notification.MessagingStyle.Message message) {
+ mMessage = message;
+ }
+
+ public Notification.MessagingStyle.Message getMessage() {
+ return mMessage;
+ }
+
+ public void setGroup(MessagingGroup group) {
+ mGroup = group;
+ }
+
+ public MessagingGroup getGroup() {
+ return mGroup;
+ }
+
+ public void setIsHistoric(boolean isHistoric) {
+ mIsHistoric = isHistoric;
+ }
+
+ public void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = mHostView.getParent();
+ mIsHidingAnimated = isHiding;
+ mHostView.invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ public View getHostView() {
+ return mHostView;
+ }
+
+ public void reset() {
+ mIsHidingAnimated = false;
+ mIsHistoric = false;
+ mGroup = null;
+ mMessage = null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
new file mode 100644
index 0000000..794cc1d
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
+
+ private static Pools.SimplePool<MessagingTextMessage> sInstancePool
+ = new Pools.SynchronizedPool<>(20);
+ private final MessagingMessageState mState = new MessagingMessageState(this);
+
+ public MessagingTextMessage(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public MessagingMessageState getState() {
+ return mState;
+ }
+
+ @Override
+ public boolean setMessage(Notification.MessagingStyle.Message message) {
+ MessagingMessage.super.setMessage(message);
+ setText(message.getText());
+ return true;
+ }
+
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+ MessagingTextMessage createdMessage = sInstancePool.acquire();
+ if (createdMessage == null) {
+ createdMessage = (MessagingTextMessage) LayoutInflater.from(
+ layout.getContext()).inflate(
+ R.layout.notification_template_messaging_text_message,
+ messagingLinearLayout,
+ false);
+ createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ }
+ createdMessage.setMessage(m);
+ return createdMessage;
+ }
+
+ public void recycle() {
+ MessagingMessage.super.recycle();
+ setAlpha(1.0f);
+ setTranslationY(0);
+ sInstancePool.release(this);
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return MEASURED_TOO_SMALL;
+ }
+ if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_NORMAL;
+ }
+ }
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ setMaxLines(lines);
+ }
+
+ @Override
+ public int getConsumedLines() {
+ return getLineCount();
+ }
+
+ public int getLayoutHeight() {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return 0;
+ }
+ return layout.getHeight();
+ }
+
+ @Override
+ public void setColor(int color) {
+ setTextColor(color);
+ }
+}
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index ad6020c..7d1c706 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -98,6 +98,8 @@
if (mPayload != null) {
dest.writeInt(mPayload.length);
dest.writeByteArray(mPayload);
+ } else {
+ dest.writeInt(0);
}
}
}
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index fb18669..95534e2 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -72,6 +72,7 @@
SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536;
private static final File TOMBSTONE_DIR = new File("/data/tombstones");
+ private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
// The pre-froyo package and class of the system updater, which
// ran in the system process. We need to remove its packages here
@@ -164,7 +165,7 @@
.append("Revision: ")
.append(SystemProperties.get("ro.revision", "")).append("\n")
.append("Bootloader: ").append(Build.BOOTLOADER).append("\n")
- .append("Radio: ").append(Build.RADIO).append("\n")
+ .append("Radio: ").append(Build.getRadioVersion()).append("\n")
.append("Kernel: ")
.append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
.append("\n").toString();
@@ -265,7 +266,7 @@
File file = new File(TOMBSTONE_DIR, path);
if (file.isFile()) {
addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE,
- "SYSTEM_TOMBSTONE");
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
@@ -299,9 +300,20 @@
timestamps.put(filename, fileTime);
+
+ String fileContents = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
+ String text = headers + fileContents + footers;
+ // Create an additional report for system server native crashes, with a special tag.
+ if (tag.equals(TAG_TOMBSTONE) && fileContents.contains(">>> system_server <<<")) {
+ addTextToDropBox(db, "system_server_native_crash", text, filename, maxSize);
+ }
+ addTextToDropBox(db, tag, text, filename, maxSize);
+ }
+
+ private static void addTextToDropBox(DropBoxManager db, String tag, String text,
+ String filename, int maxSize) {
Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
- db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") +
- footers);
+ db.addText(tag, text);
EventLog.writeEvent(DropboxLogTags.DROPBOX_FILE_COPY, filename, maxSize, tag);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 33f80ce..78a3e13 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -110,8 +110,8 @@
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
"android_util_EventLog.cpp",
- "android_util_MemoryIntArray.cpp",
"android_util_Log.cpp",
+ "android_util_MemoryIntArray.cpp",
"android_util_PathParser.cpp",
"android_util_Process.cpp",
"android_util_StringBlock.cpp",
@@ -191,6 +191,7 @@
"android_backup_FileBackupHelperBase.cpp",
"android_backup_BackupHelperDispatcher.cpp",
"android_app_backup_FullBackup.cpp",
+ "android_content_res_ApkAssets.cpp",
"android_content_res_ObbScanner.cpp",
"android_content_res_Configuration.cpp",
"android_animation_PropertyValuesHolder.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d202173..4a032c4 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -123,6 +123,7 @@
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
+extern int register_android_content_res_ApkAssets(JNIEnv* env);
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
@@ -1346,6 +1347,7 @@
REG_JNI(register_android_content_AssetManager),
REG_JNI(register_android_content_StringBlock),
REG_JNI(register_android_content_XmlBlock),
+ REG_JNI(register_android_content_res_ApkAssets),
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_Hyphenator),
REG_JNI(register_android_text_MeasuredParagraph),
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 937b3ff..ed032c7 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -28,7 +28,7 @@
#include <nativehelper/ScopedUtfChars.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_util_AssetManager.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include "Utils.h"
#include "FontUtils.h"
@@ -90,7 +90,7 @@
}
static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
- jint givenWeight, jint givenItalic) {
+ jint weight, jint italic) {
uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
for (const auto& axis : builder->axes) {
skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
@@ -114,27 +114,15 @@
std::shared_ptr<minikin::MinikinFont> minikinFont =
std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
builder->axes);
+ minikin::Font::Builder fontBuilder(minikinFont);
- int weight = givenWeight;
- bool italic = givenItalic == 1;
- if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) {
- int os2Weight;
- bool os2Italic;
- if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) {
- ALOGE("analyzeStyle failed. Using default style");
- os2Weight = 400;
- os2Italic = false;
- }
- if (givenWeight == RESOLVE_BY_FONT_TABLE) {
- weight = os2Weight;
- }
- if (givenItalic == RESOLVE_BY_FONT_TABLE) {
- italic = os2Italic;
- }
+ if (weight != RESOLVE_BY_FONT_TABLE) {
+ fontBuilder.setWeight(weight);
}
-
- builder->fonts.push_back(minikin::Font(minikinFont,
- minikin::FontStyle(weight, static_cast<minikin::FontStyle::Slant>(italic))));
+ if (italic != RESOLVE_BY_FONT_TABLE) {
+ fontBuilder.setSlant(static_cast<minikin::FontStyle::Slant>(italic != 0));
+ }
+ builder->fonts.push_back(fontBuilder.build());
builder->axes.clear();
return true;
}
@@ -217,7 +205,8 @@
NPE_CHECK_RETURN_ZERO(env, jpath);
NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
- AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
+
+ Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, jassetMgr);
if (NULL == mgr) {
builder->axes.clear();
return false;
@@ -229,27 +218,33 @@
return false;
}
- Asset* asset;
- if (isAsset) {
- asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- } else {
- asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(),
- Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+ std::unique_ptr<Asset> asset;
+ {
+ ScopedLock<AssetManager2> locked_mgr(*mgr);
+ if (isAsset) {
+ asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+ } else if (cookie > 0) {
+ // Valid java cookies are 1-based, but AssetManager cookies are 0-based.
+ asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1),
+ Asset::ACCESS_BUFFER);
+ } else {
+ asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+ }
}
- if (NULL == asset) {
+ if (nullptr == asset) {
builder->axes.clear();
return false;
}
const void* buf = asset->getBuffer(false);
if (NULL == buf) {
- delete asset;
builder->axes.clear();
return false;
}
- sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
+ sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset,
+ asset.release()));
return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 115d0d5..482d028 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -576,7 +576,7 @@
minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = paint->getTextSkewX();
bool savefakeBold = paint->isFakeBoldText();
- MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
+ MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery);
SkScalar spacing = paint->getFontMetrics(metrics);
// The populateSkPaint call may have changed fake bold / text skew
// because we want to measure with those effects applied, so now
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 09e37e1..49a24a3 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -361,7 +361,7 @@
code->sdkVersion = sdkVersion;
code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
- code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
+ code->assetManager = NdkAssetManagerForJavaObject(env, jAssetMgr);
if (obbDir != NULL) {
dirStr = env->GetStringUTFChars(obbDir, NULL);
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
new file mode 100644
index 0000000..c0f151b
--- /dev/null
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "android-base/unique_fd.h"
+#include "androidfw/ApkAssets.h"
+#include "utils/misc.h"
+
+#include "core_jni_helpers.h"
+#include "jni.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+using ::android::base::unique_fd;
+
+namespace android {
+
+static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
+ jboolean force_shared_lib, jboolean overlay) {
+ ScopedUtfChars path(env, java_path);
+ if (path.c_str() == nullptr) {
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets;
+ if (overlay) {
+ apk_assets = ApkAssets::LoadOverlay(path.c_str(), system);
+ } else if (force_shared_lib) {
+ apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
+ } else {
+ apk_assets = ApkAssets::Load(path.c_str(), system);
+ }
+
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+ jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+ ScopedUtfChars friendly_name_utf8(env, friendly_name);
+ if (friendly_name_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ unique_fd dup_fd(::dup(fd));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
+ friendly_name_utf8.c_str(),
+ system, force_shared_lib);
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
+ friendly_name_utf8.c_str(), dup_fd.get());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ delete reinterpret_cast<ApkAssets*>(ptr);
+}
+
+static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ return env->NewStringUTF(apk_assets->GetPath().c_str());
+}
+
+static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
+}
+
+static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ (void)apk_assets;
+ return JNI_TRUE;
+}
+
+static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
+ ScopedUtfChars path_utf8(env, file_name);
+ if (path_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(),
+ Asset::AccessMode::ACCESS_RANDOM);
+ if (asset == nullptr) {
+ jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
+ return 0;
+ }
+
+ // DynamicRefTable is only needed when looking up resource references. Opening an XML file
+ // directly from an ApkAssets has no notion of proper resource references.
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
+// JNI registration.
+static const JNINativeMethod gApkAssetsMethods[] = {
+ {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
+ {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+ (void*)NativeLoadFromFd},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+ {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+ {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+ {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+};
+
+int register_android_content_res_ApkAssets(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
+ arraysize(gApkAssetsMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 6b961f5..06de5da 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -573,6 +573,11 @@
minikin::Layout::purgeCaches();
}
+static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) {
+ Canvas::setCompatibilityVersion(apiLevel);
+}
+
+
}; // namespace CanvasJNI
static const JNINativeMethod gMethods[] = {
@@ -580,6 +585,7 @@
{"nInitRaster", "(Landroid/graphics/Bitmap;)J", (void*) CanvasJNI::initRaster},
{"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
{"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
+ {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion},
// ------------ @FastNative ----------------
{"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1659168..e8ef349 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -32,10 +32,13 @@
static jclass gHashMapClazz;
static jmethodID gHashMapInit;
static jmethodID gHashMapPut;
+static jclass gLongClazz;
+static jmethodID gLongValueOf;
namespace android {
using vintf::HalManifest;
+using vintf::Level;
using vintf::SchemaType;
using vintf::VintfObject;
using vintf::XmlConverter;
@@ -154,6 +157,14 @@
return jMap;
}
+static jobject android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion(JNIEnv* env, jclass) {
+ std::shared_ptr<const HalManifest> manifest = VintfObject::GetDeviceHalManifest();
+ if (manifest == nullptr || manifest->level() == Level::UNSPECIFIED) {
+ return nullptr;
+ }
+ return env->CallStaticObjectMethod(gLongClazz, gLongValueOf, static_cast<jlong>(manifest->level()));
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gVintfObjectMethods[] = {
@@ -163,6 +174,7 @@
{"getHalNamesAndVersions", "()[Ljava/lang/String;", (void*)android_os_VintfObject_getHalNamesAndVersions},
{"getSepolicyVersion", "()Ljava/lang/String;", (void*)android_os_VintfObject_getSepolicyVersion},
{"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
+ {"getTargetFrameworkCompatibilityMatrixVersion", "()Ljava/lang/Long;", (void*)android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion},
};
const char* const kVintfObjectPathName = "android/os/VintfObject";
@@ -175,6 +187,8 @@
gHashMapInit = GetMethodIDOrDie(env, gHashMapClazz, "<init>", "()V");
gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz,
"put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ gLongClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/Long"));
+ gLongValueOf = GetStaticMethodIDOrDie(env, gLongClazz, "valueOf", "(J)Ljava/lang/Long;");
return RegisterMethodsOrDie(env, kVintfObjectPathName, gVintfObjectMethods,
NELEM(gVintfObjectMethods));
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 683b4c4..2334e03 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1,1851 +1,1433 @@
-/* //device/libs/android_runtime/android_util_AssetManager.cpp
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+/*
+ * Copyright 2006, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
#define LOG_TAG "asset"
-#include <android_runtime/android_util_AssetManager.h>
-
#include <inttypes.h>
#include <linux/capability.h>
#include <stdio.h>
-#include <sys/types.h>
-#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/system_properties.h>
+#include <sys/types.h>
+#include <sys/wait.h>
#include <private/android_filesystem_config.h> // for AID_SYSTEM
-#include "androidfw/Asset.h"
-#include "androidfw/AssetManager.h"
-#include "androidfw/AttributeResolution.h"
-#include "androidfw/ResourceTypes.h"
+#include "android-base/logging.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "android_runtime/android_util_AssetManager.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_util_Binder.h"
+#include "androidfw/Asset.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/MutexGuard.h"
+#include "androidfw/ResourceTypes.h"
#include "core_jni_helpers.h"
#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedStringChars.h>
-#include <nativehelper/ScopedUtfChars.h>
+#include "nativehelper/JNIHelp.h"
+#include "nativehelper/ScopedPrimitiveArray.h"
+#include "nativehelper/ScopedStringChars.h"
+#include "nativehelper/ScopedUtfChars.h"
#include "utils/Log.h"
-#include "utils/misc.h"
#include "utils/String8.h"
+#include "utils/misc.h"
extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap);
extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
+using ::android::base::StringPrintf;
namespace android {
-static const bool kThrowOnBadId = false;
-
// ----------------------------------------------------------------------------
-static struct typedvalue_offsets_t
-{
- jfieldID mType;
- jfieldID mData;
- jfieldID mString;
- jfieldID mAssetCookie;
- jfieldID mResourceId;
- jfieldID mChangingConfigurations;
- jfieldID mDensity;
+static struct typedvalue_offsets_t {
+ jfieldID mType;
+ jfieldID mData;
+ jfieldID mString;
+ jfieldID mAssetCookie;
+ jfieldID mResourceId;
+ jfieldID mChangingConfigurations;
+ jfieldID mDensity;
} gTypedValueOffsets;
-static struct assetfiledescriptor_offsets_t
-{
- jfieldID mFd;
- jfieldID mStartOffset;
- jfieldID mLength;
+static struct assetfiledescriptor_offsets_t {
+ jfieldID mFd;
+ jfieldID mStartOffset;
+ jfieldID mLength;
} gAssetFileDescriptorOffsets;
-static struct assetmanager_offsets_t
-{
- jfieldID mObject;
+static struct assetmanager_offsets_t {
+ jfieldID mObject;
} gAssetManagerOffsets;
-static struct sparsearray_offsets_t
-{
- jclass classObject;
- jmethodID constructor;
- jmethodID put;
+static struct {
+ jfieldID native_ptr;
+} gApkAssetsFields;
+
+static struct sparsearray_offsets_t {
+ jclass classObject;
+ jmethodID constructor;
+ jmethodID put;
} gSparseArrayOffsets;
-static struct configuration_offsets_t
-{
- jclass classObject;
- jmethodID constructor;
- jfieldID mSmallestScreenWidthDpOffset;
- jfieldID mScreenWidthDpOffset;
- jfieldID mScreenHeightDpOffset;
+static struct configuration_offsets_t {
+ jclass classObject;
+ jmethodID constructor;
+ jfieldID mSmallestScreenWidthDpOffset;
+ jfieldID mScreenWidthDpOffset;
+ jfieldID mScreenHeightDpOffset;
} gConfigurationOffsets;
-jclass g_stringClass = NULL;
+jclass g_stringClass = nullptr;
// ----------------------------------------------------------------------------
-static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
- const Res_value& value, uint32_t ref, ssize_t block,
- uint32_t typeSpecFlags, ResTable_config* config = NULL);
+// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
+constexpr inline static jint ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
+ return cookie != kInvalidCookie ? static_cast<jint>(cookie + 1) : -1;
+}
-jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
- const Res_value& value, uint32_t ref, ssize_t block,
- uint32_t typeSpecFlags, ResTable_config* config)
-{
- env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
- env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
- static_cast<jint>(table->getTableCookie(block)));
- env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
- env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
- env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
- env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
- typeSpecFlags);
- if (config != NULL) {
- env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
- }
- return block;
+constexpr inline static ApkAssetsCookie JavaCookieToApkAssetsCookie(jint cookie) {
+ return cookie > 0 ? static_cast<ApkAssetsCookie>(cookie - 1) : kInvalidCookie;
}
// This is called by zygote (running as user root) as part of preloadResources.
-static void verifySystemIdmaps()
-{
- pid_t pid;
- char system_id[10];
+static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) {
+ switch (pid_t pid = fork()) {
+ case -1:
+ PLOG(ERROR) << "failed to fork for idmap";
+ break;
- snprintf(system_id, sizeof(system_id), "%d", AID_SYSTEM);
+ // child
+ case 0: {
+ struct __user_cap_header_struct capheader;
+ struct __user_cap_data_struct capdata;
- switch (pid = fork()) {
- case -1:
- ALOGE("failed to fork for idmap: %s", strerror(errno));
- break;
- case 0: // child
- {
- struct __user_cap_header_struct capheader;
- struct __user_cap_data_struct capdata;
+ memset(&capheader, 0, sizeof(capheader));
+ memset(&capdata, 0, sizeof(capdata));
- memset(&capheader, 0, sizeof(capheader));
- memset(&capdata, 0, sizeof(capdata));
+ capheader.version = _LINUX_CAPABILITY_VERSION;
+ capheader.pid = 0;
- capheader.version = _LINUX_CAPABILITY_VERSION;
- capheader.pid = 0;
+ if (capget(&capheader, &capdata) != 0) {
+ PLOG(ERROR) << "capget";
+ exit(1);
+ }
- if (capget(&capheader, &capdata) != 0) {
- ALOGE("capget: %s\n", strerror(errno));
- exit(1);
- }
+ capdata.effective = capdata.permitted;
+ if (capset(&capheader, &capdata) != 0) {
+ PLOG(ERROR) << "capset";
+ exit(1);
+ }
- capdata.effective = capdata.permitted;
- if (capset(&capheader, &capdata) != 0) {
- ALOGE("capset: %s\n", strerror(errno));
- exit(1);
- }
+ if (setgid(AID_SYSTEM) != 0) {
+ PLOG(ERROR) << "setgid";
+ exit(1);
+ }
- if (setgid(AID_SYSTEM) != 0) {
- ALOGE("setgid: %s\n", strerror(errno));
- exit(1);
- }
+ if (setuid(AID_SYSTEM) != 0) {
+ PLOG(ERROR) << "setuid";
+ exit(1);
+ }
- if (setuid(AID_SYSTEM) != 0) {
- ALOGE("setuid: %s\n", strerror(errno));
- exit(1);
- }
+ // Generic idmap parameters
+ const char* argv[8];
+ int argc = 0;
+ struct stat st;
- // Generic idmap parameters
- const char* argv[8];
- int argc = 0;
- struct stat st;
+ memset(argv, 0, sizeof(argv));
+ argv[argc++] = AssetManager::IDMAP_BIN;
+ argv[argc++] = "--scan";
+ argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
+ argv[argc++] = AssetManager::TARGET_APK_PATH;
+ argv[argc++] = AssetManager::IDMAP_DIR;
- memset(argv, NULL, sizeof(argv));
- argv[argc++] = AssetManager::IDMAP_BIN;
- argv[argc++] = "--scan";
- argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
- argv[argc++] = AssetManager::TARGET_APK_PATH;
- argv[argc++] = AssetManager::IDMAP_DIR;
-
- // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
- // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
- char subdir[PROP_VALUE_MAX];
- int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
- if (len > 0) {
- String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
- if (stat(overlayPath.string(), &st) == 0) {
- argv[argc++] = overlayPath.string();
- }
- }
- if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
- argv[argc++] = AssetManager::OVERLAY_DIR;
- }
-
- if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
- argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR;
- }
-
- // Finally, invoke idmap (if any overlay directory exists)
- if (argc > 5) {
- execv(AssetManager::IDMAP_BIN, (char* const*)argv);
- ALOGE("failed to execv for idmap: %s", strerror(errno));
- exit(1); // should never get here
- } else {
- exit(0);
- }
- }
- break;
- default: // parent
- waitpid(pid, NULL, 0);
- break;
- }
-}
-
-
-// ----------------------------------------------------------------------------
-
-// this guy is exported to other jni routines
-AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
-{
- jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject);
- AssetManager* am = reinterpret_cast<AssetManager*>(amHandle);
- if (am != NULL) {
- return am;
- }
- jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
- return NULL;
-}
-
-static jlong android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz,
- jstring fileName, jint mode)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- ALOGV("openAsset in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Empty file name");
- return -1;
- }
-
- if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
- && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
- return -1;
- }
-
- Asset* a = am->open(fileName8.c_str(), (Asset::AccessMode)mode);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return -1;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return reinterpret_cast<jlong>(a);
-}
-
-static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outOffsets)
-{
- off64_t startOffset, length;
- int fd = a->openFileDescriptor(&startOffset, &length);
- delete a;
-
- if (fd < 0) {
- jniThrowException(env, "java/io/FileNotFoundException",
- "This file can not be opened as a file descriptor; it is probably compressed");
- return NULL;
- }
-
- jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0);
- if (offsets == NULL) {
- close(fd);
- return NULL;
- }
-
- offsets[0] = startOffset;
- offsets[1] = length;
-
- env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0);
-
- jobject fileDesc = jniCreateFileDescriptor(env, fd);
- if (fileDesc == NULL) {
- close(fd);
- return NULL;
- }
-
- return newParcelFileDescriptor(env, fileDesc);
-}
-
-static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject clazz,
- jstring fileName, jlongArray outOffsets)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ALOGV("openAssetFd in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return NULL;
- }
-
- Asset* a = am->open(fileName8.c_str(), Asset::ACCESS_RANDOM);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return NULL;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return returnParcelFileDescriptor(env, a, outOffsets);
-}
-
-static jlong android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz,
- jint cookie,
- jstring fileName,
- jint mode)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return -1;
- }
-
- if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
- && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
- return -1;
- }
-
- Asset* a = cookie
- ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(),
- (Asset::AccessMode)mode)
- : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return -1;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return reinterpret_cast<jlong>(a);
-}
-
-static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jobject clazz,
- jint cookie,
- jstring fileName,
- jlongArray outOffsets)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ALOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return NULL;
- }
-
- Asset* a = cookie
- ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_RANDOM)
- : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_RANDOM);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return NULL;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return returnParcelFileDescriptor(env, a, outOffsets);
-}
-
-static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz,
- jstring fileName)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return NULL;
- }
-
- AssetDir* dir = am->openDir(fileName8.c_str());
-
- if (dir == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return NULL;
- }
-
- size_t N = dir->getFileCount();
-
- jobjectArray array = env->NewObjectArray(dir->getFileCount(),
- g_stringClass, NULL);
- if (array == NULL) {
- delete dir;
- return NULL;
- }
-
- for (size_t i=0; i<N; i++) {
- const String8& name = dir->getFileName(i);
- jstring str = env->NewStringUTF(name.string());
- if (str == NULL) {
- delete dir;
- return NULL;
+ // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
+ // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
+ std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY,
+ "");
+ if (!overlay_theme_path.empty()) {
+ overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path;
+ if (stat(overlay_theme_path.c_str(), &st) == 0) {
+ argv[argc++] = overlay_theme_path.c_str();
}
- env->SetObjectArrayElement(array, i, str);
- env->DeleteLocalRef(str);
- }
+ }
- delete dir;
+ if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
+ argv[argc++] = AssetManager::OVERLAY_DIR;
+ }
- return array;
+ if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
+ argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR;
+ }
+
+ // Finally, invoke idmap (if any overlay directory exists)
+ if (argc > 5) {
+ execv(AssetManager::IDMAP_BIN, (char* const*)argv);
+ PLOG(ERROR) << "failed to execv for idmap";
+ exit(1); // should never get here
+ } else {
+ exit(0);
+ }
+ } break;
+
+ // parent
+ default:
+ waitpid(pid, nullptr, 0);
+ break;
+ }
}
-static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- //printf("Destroying Asset Stream: %p\n", a);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return;
- }
-
- delete a;
-}
-
-static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- uint8_t b;
- ssize_t res = a->read(&b, 1);
- return res == 1 ? b : -1;
-}
-
-static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz,
- jlong assetHandle, jbyteArray bArray,
- jint off, jint len)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL || bArray == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- if (len == 0) {
- return 0;
- }
-
- jsize bLen = env->GetArrayLength(bArray);
- if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
- return -1;
- }
-
- jbyte* b = env->GetByteArrayElements(bArray, NULL);
- ssize_t res = a->read(b+off, len);
- env->ReleaseByteArrayElements(bArray, b, 0);
-
- if (res > 0) return static_cast<jint>(res);
-
- if (res < 0) {
- jniThrowException(env, "java/io/IOException", "");
- }
- return -1;
-}
-
-static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz,
- jlong assetHandle,
- jlong offset, jint whence)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- return a->seek(
- offset, (whence > 0) ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR));
-}
-
-static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- return a->getLength();
-}
-
-static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- return a->getRemainingLength();
-}
-
-static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
- jstring path, jboolean appAsLib)
-{
- ScopedUtfChars path8(env, path);
- if (path8.c_str() == NULL) {
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- int32_t cookie;
- bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib);
-
- return (res) ? static_cast<jint>(cookie) : 0;
-}
-
-static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
- jstring idmapPath)
-{
- ScopedUtfChars idmapPath8(env, idmapPath);
- if (idmapPath8.c_str() == NULL) {
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- int32_t cookie;
- bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);
-
- return (res) ? (jint)cookie : 0;
-}
-
-static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz,
- jobject fileDescriptor, jstring debugPathName,
- jboolean appAsLib)
-{
- ScopedUtfChars debugPathName8(env, debugPathName);
-
- int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
- if (fd < 0) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- int dupfd = ::dup(fd);
- if (dupfd < 0) {
- jniThrowIOException(env, errno);
- return 0;
- }
-
- int32_t cookie;
- bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib);
-
- return (res) ? static_cast<jint>(cookie) : 0;
-}
-
-static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return JNI_TRUE;
- }
- return am->isUpToDate() ? JNI_TRUE : JNI_FALSE;
-}
-
-static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales)
-{
- Vector<String8> locales;
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- am->getLocales(&locales, includeSystemLocales);
-
- const int N = locales.size();
-
- jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);
- if (result == NULL) {
- return NULL;
- }
-
- for (int i=0; i<N; i++) {
- jstring str = env->NewStringUTF(locales[i].string());
- if (str == NULL) {
- return NULL;
- }
- env->SetObjectArrayElement(result, i, str);
- env->DeleteLocalRef(str);
- }
-
- return result;
-}
-
-static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
-{
- return getLocales(env, clazz, true /* include system locales */);
-}
-
-static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz)
-{
- return getLocales(env, clazz, false /* don't include system locales */);
-}
-
-static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
- jobject result = env->NewObject(gConfigurationOffsets.classObject,
- gConfigurationOffsets.constructor);
- if (result == NULL) {
- return NULL;
- }
-
- env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset,
- config.smallestScreenWidthDp);
- env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
- env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
-
- return result;
-}
-
-static jobjectArray getSizeConfigurationsInternal(JNIEnv* env,
- const Vector<ResTable_config>& configs) {
- const int N = configs.size();
- jobjectArray result = env->NewObjectArray(N, gConfigurationOffsets.classObject, NULL);
- if (result == NULL) {
- return NULL;
- }
-
- for (int i=0; i<N; i++) {
- jobject config = constructConfigurationObject(env, configs[i]);
- if (config == NULL) {
- env->DeleteLocalRef(result);
- return NULL;
- }
-
- env->SetObjectArrayElement(result, i, config);
- env->DeleteLocalRef(config);
- }
-
- return result;
-}
-
-static jobjectArray android_content_AssetManager_getSizeConfigurations(JNIEnv* env, jobject clazz) {
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- const ResTable& res(am->getResources());
- Vector<ResTable_config> configs;
- res.getConfigurations(&configs, false /* ignoreMipmap */, true /* ignoreAndroidPackage */);
-
- return getSizeConfigurationsInternal(env, configs);
-}
-
-static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
- jint mcc, jint mnc,
- jstring locale, jint orientation,
- jint touchscreen, jint density,
- jint keyboard, jint keyboardHidden,
- jint navigation,
- jint screenWidth, jint screenHeight,
- jint smallestScreenWidthDp,
- jint screenWidthDp, jint screenHeightDp,
- jint screenLayout, jint uiMode,
- jint colorMode, jint sdkVersion)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return;
- }
-
- ResTable_config config;
- memset(&config, 0, sizeof(config));
-
- const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
-
- // Constants duplicated from Java class android.content.res.Configuration.
- static const jint kScreenLayoutRoundMask = 0x300;
- static const jint kScreenLayoutRoundShift = 8;
-
- config.mcc = (uint16_t)mcc;
- config.mnc = (uint16_t)mnc;
- config.orientation = (uint8_t)orientation;
- config.touchscreen = (uint8_t)touchscreen;
- config.density = (uint16_t)density;
- config.keyboard = (uint8_t)keyboard;
- config.inputFlags = (uint8_t)keyboardHidden;
- config.navigation = (uint8_t)navigation;
- config.screenWidth = (uint16_t)screenWidth;
- config.screenHeight = (uint16_t)screenHeight;
- config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp;
- config.screenWidthDp = (uint16_t)screenWidthDp;
- config.screenHeightDp = (uint16_t)screenHeightDp;
- config.screenLayout = (uint8_t)screenLayout;
- config.uiMode = (uint8_t)uiMode;
- config.colorMode = (uint8_t)colorMode;
- config.sdkVersion = (uint16_t)sdkVersion;
- config.minorVersion = 0;
-
- // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
- // in C++. We must extract the round qualifier out of the Java screenLayout and put it
- // into screenLayout2.
- config.screenLayout2 =
- (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
-
- am->setConfiguration(config, locale8);
-
- if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
-}
-
-static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz,
- jstring name,
- jstring defType,
- jstring defPackage)
-{
- ScopedStringChars name16(env, name);
- if (name16.get() == NULL) {
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- const char16_t* defType16 = reinterpret_cast<const char16_t*>(defType)
- ? reinterpret_cast<const char16_t*>(env->GetStringChars(defType, NULL))
- : NULL;
- jsize defTypeLen = defType
- ? env->GetStringLength(defType) : 0;
- const char16_t* defPackage16 = reinterpret_cast<const char16_t*>(defPackage)
- ? reinterpret_cast<const char16_t*>(env->GetStringChars(defPackage,
- NULL))
- : NULL;
- jsize defPackageLen = defPackage
- ? env->GetStringLength(defPackage) : 0;
-
- jint ident = am->getResources().identifierForName(
- reinterpret_cast<const char16_t*>(name16.get()), name16.size(),
- defType16, defTypeLen, defPackage16, defPackageLen);
-
- if (defPackage16) {
- env->ReleaseStringChars(defPackage,
- reinterpret_cast<const jchar*>(defPackage16));
- }
- if (defType16) {
- env->ReleaseStringChars(defType,
- reinterpret_cast<const jchar*>(defType16));
- }
-
- return ident;
-}
-
-static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- String16 str;
- if (name.package != NULL) {
- str.setTo(name.package, name.packageLen);
- }
- if (name.type8 != NULL || name.type != NULL) {
- if (str.size() > 0) {
- char16_t div = ':';
- str.append(&div, 1);
- }
- if (name.type8 != NULL) {
- str.append(String16(name.type8, name.typeLen));
- } else {
- str.append(name.type, name.typeLen);
- }
- }
- if (name.name8 != NULL || name.name != NULL) {
- if (str.size() > 0) {
- char16_t div = '/';
- str.append(&div, 1);
- }
- if (name.name8 != NULL) {
- str.append(String16(name.name8, name.nameLen));
- } else {
- str.append(name.name, name.nameLen);
- }
- }
-
- return env->NewString((const jchar*)str.string(), str.size());
-}
-
-static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- if (name.package != NULL) {
- return env->NewString((const jchar*)name.package, name.packageLen);
- }
-
- return NULL;
-}
-
-static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- if (name.type8 != NULL) {
- return env->NewStringUTF(name.type8);
- }
-
- if (name.type != NULL) {
- return env->NewString((const jchar*)name.type, name.typeLen);
- }
-
- return NULL;
-}
-
-static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- if (name.name8 != NULL) {
- return env->NewStringUTF(name.name8);
- }
-
- if (name.name != NULL) {
- return env->NewString((const jchar*)name.name, name.nameLen);
- }
-
- return NULL;
-}
-
-static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
- jint ident,
- jshort density,
- jobject outValue,
- jboolean resolve)
-{
- if (outValue == NULL) {
- jniThrowNullPointerException(env, "outValue");
- return 0;
- }
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- const ResTable& res(am->getResources());
-
- Res_value value;
- ResTable_config config;
- uint32_t typeSpecFlags;
- ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- uint32_t ref = ident;
- if (resolve) {
- block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- }
- if (block >= 0) {
- return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
- }
-
- return static_cast<jint>(block);
-}
-
-static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz,
- jint ident, jint bagEntryId,
- jobject outValue, jboolean resolve)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- const ResTable& res(am->getResources());
-
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- ssize_t block = -1;
- Res_value value;
-
- const ResTable::bag_entry* entry = NULL;
- uint32_t typeSpecFlags;
- ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags);
-
- for (ssize_t i=0; i<entryCount; i++) {
- if (((uint32_t)bagEntryId) == entry->map.name.ident) {
- block = entry->stringBlock;
- value = entry->map.value;
- }
- entry++;
- }
-
- res.unlock();
-
- if (block < 0) {
- return static_cast<jint>(block);
- }
-
- uint32_t ref = ident;
- if (resolve) {
- block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- }
- if (block >= 0) {
- return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags);
- }
-
- return static_cast<jint>(block);
-}
-
-static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- return am->getResources().getTableCount();
-}
-
-static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz,
- jint block)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block));
-}
-
-static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz,
- jint cookie)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- String8 name(am->getAssetPath(static_cast<int32_t>(cookie)));
- if (name.length() == 0) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name");
- return NULL;
- }
- jstring str = env->NewStringUTF(name.string());
- return str;
-}
-
-static jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- const ResTable& res = am->getResources();
-
- jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject,
- gSparseArrayOffsets.constructor);
- const size_t N = res.getBasePackageCount();
- for (size_t i = 0; i < N; i++) {
- const String16 name = res.getBasePackageName(i);
- env->CallVoidMethod(
- sparseArray, gSparseArrayOffsets.put,
- static_cast<jint>(res.getBasePackageId(i)),
- env->NewString(reinterpret_cast<const jchar*>(name.string()),
- name.size()));
- }
- return sparseArray;
-}
-
-static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- return reinterpret_cast<jlong>(new ResTable::Theme(am->getResources()));
-}
-
-static void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz,
- jlong themeHandle)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- delete theme;
-}
-
-static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz,
- jlong themeHandle,
- jint styleRes,
- jboolean force)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- theme->applyStyle(styleRes, force ? true : false);
-}
-
-static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz,
- jlong destHandle, jlong srcHandle)
-{
- ResTable::Theme* dest = reinterpret_cast<ResTable::Theme*>(destHandle);
- ResTable::Theme* src = reinterpret_cast<ResTable::Theme*>(srcHandle);
- dest->setTo(*src);
-}
-
-static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- theme->clear();
-}
-
-static jint android_content_AssetManager_loadThemeAttributeValue(
- JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- const ResTable& res(theme->getResTable());
-
- Res_value value;
- // XXX value could be different in different configs!
- uint32_t typeSpecFlags = 0;
- ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags);
- uint32_t ref = 0;
- if (resolve) {
- block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- }
- return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
-}
-
-static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz,
- jlong themeHandle)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- return theme->getChangingConfigurations();
-}
-
-static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz,
- jlong themeHandle, jint pri,
- jstring tag, jstring prefix)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- const ResTable& res(theme->getResTable());
- (void)res;
-
- // XXX Need to use params.
- theme->dumpToLog();
-}
-
-static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz,
- jlong themeToken,
- jint defStyleAttr,
- jint defStyleRes,
- jintArray inValues,
- jintArray attrs,
- jintArray outValues,
- jintArray outIndices)
-{
- if (themeToken == 0) {
- jniThrowNullPointerException(env, "theme token");
- return JNI_FALSE;
- }
- if (attrs == NULL) {
- jniThrowNullPointerException(env, "attrs");
- return JNI_FALSE;
- }
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- const jsize NI = env->GetArrayLength(attrs);
- const jsize NV = env->GetArrayLength(outValues);
- if (NV < (NI*STYLE_NUM_ENTRIES)) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
- return JNI_FALSE;
- }
-
- jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
- if (src == NULL) {
- return JNI_FALSE;
- }
-
- jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0);
- const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues);
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- if (baseDest == NULL) {
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return JNI_FALSE;
- }
-
- jint* indices = NULL;
- if (outIndices != NULL) {
- if (env->GetArrayLength(outIndices) > NI) {
- indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
- }
- }
-
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes,
- (uint32_t*) srcValues, NSV,
- (uint32_t*) src, NI,
- (uint32_t*) baseDest,
- (uint32_t*) indices);
-
- if (indices != NULL) {
- env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
- }
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
- env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0);
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return result ? JNI_TRUE : JNI_FALSE;
-}
-
-static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken,
- jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length,
- jlong outValuesAddress, jlong outIndicesAddress) {
- jint* attrs = env->GetIntArrayElements(attrsObj, 0);
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
- uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress));
- uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress));
- ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
- reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices);
- env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT);
-}
-
-static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz,
- jlong xmlParserToken,
- jintArray attrs,
- jintArray outValues,
- jintArray outIndices)
-{
- if (xmlParserToken == 0) {
- jniThrowNullPointerException(env, "xmlParserToken");
- return JNI_FALSE;
- }
- if (attrs == NULL) {
- jniThrowNullPointerException(env, "attrs");
- return JNI_FALSE;
- }
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return JNI_FALSE;
- }
- const ResTable& res(am->getResources());
- ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken;
-
- const jsize NI = env->GetArrayLength(attrs);
- const jsize NV = env->GetArrayLength(outValues);
- if (NV < (NI*STYLE_NUM_ENTRIES)) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
- return JNI_FALSE;
- }
-
- jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
- if (src == NULL) {
- return JNI_FALSE;
- }
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- if (baseDest == NULL) {
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return JNI_FALSE;
- }
-
- jint* indices = NULL;
- if (outIndices != NULL) {
- if (env->GetArrayLength(outIndices) > NI) {
- indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
- }
- }
-
- bool result = RetrieveAttributes(&res, xmlParser,
- (uint32_t*) src, NI,
- (uint32_t*) baseDest,
- (uint32_t*) indices);
-
- if (indices != NULL) {
- env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
- }
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return result ? JNI_TRUE : JNI_FALSE;
-}
-
-static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
- jint id)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- const ResTable& res(am->getResources());
-
- res.lock();
- const ResTable::bag_entry* defStyleEnt = NULL;
- ssize_t bagOff = res.getBagLocked(id, &defStyleEnt);
- res.unlock();
-
- return static_cast<jint>(bagOff);
-}
-
-static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz,
- jint id,
- jintArray outValues)
-{
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return JNI_FALSE;
- }
- const ResTable& res(am->getResources());
- ResTable_config config;
- Res_value value;
- ssize_t block;
-
- const jsize NV = env->GetArrayLength(outValues);
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- jint* dest = baseDest;
- if (dest == NULL) {
- jniThrowException(env, "java/lang/OutOfMemoryError", "");
- return JNI_FALSE;
- }
-
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- const ResTable::bag_entry* arrayEnt = NULL;
- uint32_t arrayTypeSetFlags = 0;
- ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags);
- const ResTable::bag_entry* endArrayEnt = arrayEnt +
- (bagOff >= 0 ? bagOff : 0);
-
- int i = 0;
- uint32_t typeSetFlags;
- while (i < NV && arrayEnt < endArrayEnt) {
- block = arrayEnt->stringBlock;
- typeSetFlags = arrayTypeSetFlags;
- config.density = 0;
- value = arrayEnt->map.value;
-
- uint32_t resid = 0;
- if (value.dataType != Res_value::TYPE_NULL) {
- // Take care of resolving the found resource to its final value.
- //printf("Resolving attribute reference\n");
- ssize_t newBlock = res.resolveReference(&value, block, &resid,
- &typeSetFlags, &config);
- if (kThrowOnBadId) {
- if (newBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return JNI_FALSE;
- }
- }
- if (newBlock >= 0) block = newBlock;
- }
-
- // Deal with the special @null value -- it turns back to TYPE_NULL.
- if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- }
-
- //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
-
- // Write the final value back to Java.
- dest[STYLE_TYPE] = value.dataType;
- dest[STYLE_DATA] = value.data;
- dest[STYLE_ASSET_COOKIE] = reinterpret_cast<jint>(res.getTableCookie(block));
- dest[STYLE_RESOURCE_ID] = resid;
- dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- dest[STYLE_DENSITY] = config.density;
- dest += STYLE_NUM_ENTRIES;
- i+= STYLE_NUM_ENTRIES;
- arrayEnt++;
- }
-
- i /= STYLE_NUM_ENTRIES;
-
- res.unlock();
-
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
-
- return i;
-}
-
-static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
- jint cookie,
- jstring fileName)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return 0;
- }
-
- int32_t assetCookie = static_cast<int32_t>(cookie);
- Asset* a = assetCookie
- ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER)
- : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return 0;
- }
-
- const DynamicRefTable* dynamicRefTable =
- am->getResources().getDynamicRefTableForCookie(assetCookie);
- ResXMLTree* block = new ResXMLTree(dynamicRefTable);
- status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
- a->close();
- delete a;
-
- if (err != NO_ERROR) {
- jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
- return 0;
- }
-
- return reinterpret_cast<jlong>(block);
-}
-
-static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz,
- jint arrayResId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(arrayResId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jintArray array = env->NewIntArray(N * 2);
- if (array == NULL) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- Res_value value;
- const ResTable::bag_entry* bag = startOfBag;
- for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) {
- jint stringIndex = -1;
- jint stringBlock = 0;
- value = bag->map.value;
-
- // Take care of resolving the found resource to its final value.
- stringBlock = res.resolveReference(&value, bag->stringBlock, NULL);
- if (value.dataType == Res_value::TYPE_STRING) {
- stringIndex = value.data;
- }
-
- if (kThrowOnBadId) {
- if (stringBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return array;
- }
- }
-
- //todo: It might be faster to allocate a C array to contain
- // the blocknums and indices, put them in there and then
- // do just one SetIntArrayRegion()
- env->SetIntArrayRegion(array, j, 1, &stringBlock);
- env->SetIntArrayRegion(array, j + 1, 1, &stringIndex);
- j = j + 2;
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
- jint arrayResId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(arrayResId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL);
- if (env->ExceptionCheck()) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- Res_value value;
- const ResTable::bag_entry* bag = startOfBag;
- size_t strLen = 0;
- for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
- value = bag->map.value;
- jstring str = NULL;
-
- // Take care of resolving the found resource to its final value.
- ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return array;
- }
- }
- if (value.dataType == Res_value::TYPE_STRING) {
- const ResStringPool* pool = res.getTableStringBlock(block);
- const char* str8 = pool->string8At(value.data, &strLen);
- if (str8 != NULL) {
- str = env->NewStringUTF(str8);
- } else {
- const char16_t* str16 = pool->stringAt(value.data, &strLen);
- str = env->NewString(reinterpret_cast<const jchar*>(str16),
- strLen);
- }
-
- // If one of our NewString{UTF} calls failed due to memory, an
- // exception will be pending.
- if (env->ExceptionCheck()) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- env->SetObjectArrayElement(array, i, str);
-
- // str is not NULL at that point, otherwise ExceptionCheck would have been true.
- // If we have a large amount of strings in our array, we might
- // overflow the local reference table of the VM.
- env->DeleteLocalRef(str);
- }
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz,
- jint arrayResId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(arrayResId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jintArray array = env->NewIntArray(N);
- if (array == NULL) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- Res_value value;
- const ResTable::bag_entry* bag = startOfBag;
- for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
- value = bag->map.value;
-
- // Take care of resolving the found resource to its final value.
- ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return array;
- }
- }
- if (value.dataType >= Res_value::TYPE_FIRST_INT
- && value.dataType <= Res_value::TYPE_LAST_INT) {
- int intVal = value.data;
- env->SetIntArrayRegion(array, i, 1, &intVal);
- }
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz,
- jint styleId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(styleId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jintArray array = env->NewIntArray(N);
- if (array == NULL) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- const ResTable::bag_entry* bag = startOfBag;
- for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
- int resourceId = bag->map.name.ident;
- env->SetIntArrayRegion(array, i, 1, &resourceId);
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
-{
- if (isSystem) {
- verifySystemIdmaps();
- }
- AssetManager* am = new AssetManager();
- if (am == NULL) {
- jniThrowException(env, "java/lang/OutOfMemoryError", "");
- return;
- }
-
- am->addDefaultAssets();
-
- ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
- env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
-}
-
-static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = (AssetManager*)
- (env->GetLongField(clazz, gAssetManagerOffsets.mObject));
- ALOGV("Destroying AssetManager %p for Java object %p\n", am, clazz);
- if (am != NULL) {
- delete am;
- env->SetLongField(clazz, gAssetManagerOffsets.mObject, 0);
- }
-}
-
-static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz)
-{
- return Asset::getGlobalCount();
-}
-
-static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, jobject clazz)
-{
- String8 alloc = Asset::getAssetAllocations();
- if (alloc.length() <= 0) {
- return NULL;
- }
-
- jstring str = env->NewStringUTF(alloc.string());
- return str;
-}
-
-static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz)
-{
- return AssetManager::getGlobalCount();
+static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref,
+ uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) {
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType);
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie,
+ ApkAssetsCookieToJavaCookie(cookie));
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data);
+ env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr);
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref);
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags);
+ if (config != nullptr) {
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density);
+ }
+ return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie));
}
// ----------------------------------------------------------------------------
-/*
- * JNI registration.
- */
-static const JNINativeMethod gAssetManagerMethods[] = {
- /* name, signature, funcPtr */
-
- // Basic asset stuff.
- { "openAsset", "(Ljava/lang/String;I)J",
- (void*) android_content_AssetManager_openAsset },
- { "openAssetFd", "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*) android_content_AssetManager_openAssetFd },
- { "openNonAssetNative", "(ILjava/lang/String;I)J",
- (void*) android_content_AssetManager_openNonAssetNative },
- { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*) android_content_AssetManager_openNonAssetFdNative },
- { "list", "(Ljava/lang/String;)[Ljava/lang/String;",
- (void*) android_content_AssetManager_list },
- { "destroyAsset", "(J)V",
- (void*) android_content_AssetManager_destroyAsset },
- { "readAssetChar", "(J)I",
- (void*) android_content_AssetManager_readAssetChar },
- { "readAsset", "(J[BII)I",
- (void*) android_content_AssetManager_readAsset },
- { "seekAsset", "(JJI)J",
- (void*) android_content_AssetManager_seekAsset },
- { "getAssetLength", "(J)J",
- (void*) android_content_AssetManager_getAssetLength },
- { "getAssetRemainingLength", "(J)J",
- (void*) android_content_AssetManager_getAssetRemainingLength },
- { "addAssetPathNative", "(Ljava/lang/String;Z)I",
- (void*) android_content_AssetManager_addAssetPath },
- { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I",
- (void*) android_content_AssetManager_addAssetFd },
- { "addOverlayPathNative", "(Ljava/lang/String;)I",
- (void*) android_content_AssetManager_addOverlayPath },
- { "isUpToDate", "()Z",
- (void*) android_content_AssetManager_isUpToDate },
-
- // Resources.
- { "getLocales", "()[Ljava/lang/String;",
- (void*) android_content_AssetManager_getLocales },
- { "getNonSystemLocales", "()[Ljava/lang/String;",
- (void*) android_content_AssetManager_getNonSystemLocales },
- { "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
- (void*) android_content_AssetManager_getSizeConfigurations },
- { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V",
- (void*) android_content_AssetManager_setConfiguration },
- { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
- (void*) android_content_AssetManager_getResourceIdentifier },
- { "getResourceName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourceName },
- { "getResourcePackageName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourcePackageName },
- { "getResourceTypeName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourceTypeName },
- { "getResourceEntryName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourceEntryName },
- { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
- (void*) android_content_AssetManager_loadResourceValue },
- { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
- (void*) android_content_AssetManager_loadResourceBagValue },
- { "getStringBlockCount","()I",
- (void*) android_content_AssetManager_getStringBlockCount },
- { "getNativeStringBlock","(I)J",
- (void*) android_content_AssetManager_getNativeStringBlock },
- { "getCookieName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getCookieName },
- { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;",
- (void*) android_content_AssetManager_getAssignedPackageIdentifiers },
-
- // Themes.
- { "newTheme", "()J",
- (void*) android_content_AssetManager_newTheme },
- { "deleteTheme", "(J)V",
- (void*) android_content_AssetManager_deleteTheme },
- { "applyThemeStyle", "(JIZ)V",
- (void*) android_content_AssetManager_applyThemeStyle },
- { "copyTheme", "(JJ)V",
- (void*) android_content_AssetManager_copyTheme },
- { "clearTheme", "(J)V",
- (void*) android_content_AssetManager_clearTheme },
- { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
- (void*) android_content_AssetManager_loadThemeAttributeValue },
- { "getThemeChangingConfigurations", "(J)I",
- (void*) android_content_AssetManager_getThemeChangingConfigurations },
- { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
- (void*) android_content_AssetManager_dumpTheme },
- { "applyStyle","(JIIJ[IIJJ)V",
- (void*) android_content_AssetManager_applyStyle },
- { "resolveAttrs","(JII[I[I[I[I)Z",
- (void*) android_content_AssetManager_resolveAttrs },
- { "retrieveAttributes","(J[I[I[I)Z",
- (void*) android_content_AssetManager_retrieveAttributes },
- { "getArraySize","(I)I",
- (void*) android_content_AssetManager_getArraySize },
- { "retrieveArray","(I[I)I",
- (void*) android_content_AssetManager_retrieveArray },
-
- // XML files.
- { "openXmlAssetNative", "(ILjava/lang/String;)J",
- (void*) android_content_AssetManager_openXmlAssetNative },
-
- // Arrays.
- { "getArrayStringResource","(I)[Ljava/lang/String;",
- (void*) android_content_AssetManager_getArrayStringResource },
- { "getArrayStringInfo","(I)[I",
- (void*) android_content_AssetManager_getArrayStringInfo },
- { "getArrayIntResource","(I)[I",
- (void*) android_content_AssetManager_getArrayIntResource },
- { "getStyleAttributes","(I)[I",
- (void*) android_content_AssetManager_getStyleAttributes },
-
- // Bookkeeping.
- { "init", "(Z)V",
- (void*) android_content_AssetManager_init },
- { "destroy", "()V",
- (void*) android_content_AssetManager_destroy },
- { "getGlobalAssetCount", "()I",
- (void*) android_content_AssetManager_getGlobalAssetCount },
- { "getAssetAllocations", "()Ljava/lang/String;",
- (void*) android_content_AssetManager_getAssetAllocations },
- { "getGlobalAssetManagerCount", "()I",
- (void*) android_content_AssetManager_getGlobalAssetManagerCount },
+// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
+struct GuardedAssetManager : public ::AAssetManager {
+ Guarded<AssetManager2> guarded_assetmanager;
};
-int register_android_content_AssetManager(JNIEnv* env)
-{
- jclass typedValue = FindClassOrDie(env, "android/util/TypedValue");
- gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I");
- gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I");
- gTypedValueOffsets.mString = GetFieldIDOrDie(env, typedValue, "string",
- "Ljava/lang/CharSequence;");
- gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I");
- gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I");
- gTypedValueOffsets.mChangingConfigurations = GetFieldIDOrDie(env, typedValue,
- "changingConfigurations", "I");
- gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+::AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) {
+ jlong assetmanager_handle = env->GetLongField(jassetmanager, gAssetManagerOffsets.mObject);
+ ::AAssetManager* am = reinterpret_cast<::AAssetManager*>(assetmanager_handle);
+ if (am == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
+ return nullptr;
+ }
+ return am;
+}
- jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
- gAssetFileDescriptorOffsets.mFd = GetFieldIDOrDie(env, assetFd, "mFd",
- "Landroid/os/ParcelFileDescriptor;");
- gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
- gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) {
+ if (assetmanager == nullptr) {
+ return nullptr;
+ }
+ return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager;
+}
- jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
- gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
+Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) {
+ return AssetManagerForNdkAssetManager(NdkAssetManagerForJavaObject(env, jassetmanager));
+}
- jclass stringClass = FindClassOrDie(env, "java/lang/String");
- g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
+ return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
+}
- jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray");
- gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass);
- gSparseArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject,
- "<init>", "()V");
- gSparseArrayOffsets.put = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put",
- "(ILjava/lang/Object;)V");
+static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset,
+ jlongArray out_offsets) {
+ off64_t start_offset, length;
+ int fd = asset->openFileDescriptor(&start_offset, &length);
+ asset.reset();
- jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration");
- gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass);
- gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass,
- "<init>", "()V");
- gConfigurationOffsets.mSmallestScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass,
- "smallestScreenWidthDp", "I");
- gConfigurationOffsets.mScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass,
- "screenWidthDp", "I");
- gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass,
- "screenHeightDp", "I");
+ if (fd < 0) {
+ jniThrowException(env, "java/io/FileNotFoundException",
+ "This file can not be opened as a file descriptor; it is probably "
+ "compressed");
+ return nullptr;
+ }
- return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
- NELEM(gAssetManagerMethods));
+ jlong* offsets = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(out_offsets, 0));
+ if (offsets == nullptr) {
+ close(fd);
+ return nullptr;
+ }
+
+ offsets[0] = start_offset;
+ offsets[1] = length;
+
+ env->ReleasePrimitiveArrayCritical(out_offsets, offsets, 0);
+
+ jobject file_desc = jniCreateFileDescriptor(env, fd);
+ if (file_desc == nullptr) {
+ close(fd);
+ return nullptr;
+ }
+ return newParcelFileDescriptor(env, file_desc);
+}
+
+static jint NativeGetGlobalAssetCount(JNIEnv* /*env*/, jobject /*clazz*/) {
+ return Asset::getGlobalCount();
+}
+
+static jobject NativeGetAssetAllocations(JNIEnv* env, jobject /*clazz*/) {
+ String8 alloc = Asset::getAssetAllocations();
+ if (alloc.length() <= 0) {
+ return nullptr;
+ }
+ return env->NewStringUTF(alloc.string());
+}
+
+static jint NativeGetGlobalAssetManagerCount(JNIEnv* /*env*/, jobject /*clazz*/) {
+ // TODO(adamlesinski): Switch to AssetManager2.
+ return AssetManager::getGlobalCount();
+}
+
+static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
+ // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and
+ // AssetManager2 in a contiguous block (GuardedAssetManager).
+ return reinterpret_cast<jlong>(new GuardedAssetManager());
+}
+
+static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ delete reinterpret_cast<GuardedAssetManager*>(ptr);
+}
+
+static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jobjectArray apk_assets_array, jboolean invalidate_caches) {
+ const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
+ std::vector<const ApkAssets*> apk_assets;
+ apk_assets.reserve(apk_assets_len);
+ for (jsize i = 0; i < apk_assets_len; i++) {
+ jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
+ if (obj == nullptr) {
+ std::string msg = StringPrintf("ApkAssets at index %d is null", i);
+ jniThrowNullPointerException(env, msg.c_str());
+ return;
+ }
+
+ jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ assetmanager->SetApkAssets(apk_assets, invalidate_caches);
+}
+
+static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
+ jstring locale, jint orientation, jint touchscreen, jint density,
+ jint keyboard, jint keyboard_hidden, jint navigation,
+ jint screen_width, jint screen_height,
+ jint smallest_screen_width_dp, jint screen_width_dp,
+ jint screen_height_dp, jint screen_layout, jint ui_mode,
+ jint color_mode, jint major_version) {
+ ResTable_config configuration;
+ memset(&configuration, 0, sizeof(configuration));
+ configuration.mcc = static_cast<uint16_t>(mcc);
+ configuration.mnc = static_cast<uint16_t>(mnc);
+ configuration.orientation = static_cast<uint8_t>(orientation);
+ configuration.touchscreen = static_cast<uint8_t>(touchscreen);
+ configuration.density = static_cast<uint16_t>(density);
+ configuration.keyboard = static_cast<uint8_t>(keyboard);
+ configuration.inputFlags = static_cast<uint8_t>(keyboard_hidden);
+ configuration.navigation = static_cast<uint8_t>(navigation);
+ configuration.screenWidth = static_cast<uint16_t>(screen_width);
+ configuration.screenHeight = static_cast<uint16_t>(screen_height);
+ configuration.smallestScreenWidthDp = static_cast<uint16_t>(smallest_screen_width_dp);
+ configuration.screenWidthDp = static_cast<uint16_t>(screen_width_dp);
+ configuration.screenHeightDp = static_cast<uint16_t>(screen_height_dp);
+ configuration.screenLayout = static_cast<uint8_t>(screen_layout);
+ configuration.uiMode = static_cast<uint8_t>(ui_mode);
+ configuration.colorMode = static_cast<uint8_t>(color_mode);
+ configuration.sdkVersion = static_cast<uint16_t>(major_version);
+
+ if (locale != nullptr) {
+ ScopedUtfChars locale_utf8(env, locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ configuration.setBcp47Locale(locale_utf8.c_str());
+ }
+
+ // Constants duplicated from Java class android.content.res.Configuration.
+ static const jint kScreenLayoutRoundMask = 0x300;
+ static const jint kScreenLayoutRoundShift = 8;
+
+ // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
+ // in C++. We must extract the round qualifier out of the Java screenLayout and put it
+ // into screenLayout2.
+ configuration.screenLayout2 =
+ static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ assetmanager->SetConfiguration(configuration);
+}
+
+static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+
+ jobject sparse_array =
+ env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor);
+
+ if (sparse_array == nullptr) {
+ // An exception is pending.
+ return nullptr;
+ }
+
+ assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) {
+ jstring jpackage_name = env->NewStringUTF(package_name.c_str());
+ if (jpackage_name == nullptr) {
+ // An exception is pending.
+ return;
+ }
+
+ env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id),
+ jpackage_name);
+ });
+ return sparse_array;
+}
+
+static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) {
+ ScopedUtfChars path_utf8(env, path);
+ if (path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return nullptr;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<AssetDir> asset_dir =
+ assetmanager->OpenDir(path_utf8.c_str());
+ if (asset_dir == nullptr) {
+ jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
+ return nullptr;
+ }
+
+ const size_t file_count = asset_dir->getFileCount();
+
+ jobjectArray array = env->NewObjectArray(file_count, g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < file_count; i++) {
+ jstring java_string = env->NewStringUTF(asset_dir->getFileName(i).string());
+
+ // Check for errors creating the strings (if malformed or no memory).
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(array, i, java_string);
+
+ // If we have a large amount of string in our array, we might overflow the
+ // local reference table of the VM.
+ env->DeleteLocalRef(java_string);
+ }
+ return array;
+}
+
+static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
+ jint access_mode) {
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
+ access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
+ return 0;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset =
+ assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode));
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(asset.release());
+}
+
+static jobject NativeOpenAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
+ jlongArray out_offsets) {
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return nullptr;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return nullptr;
+ }
+ return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets);
+}
+
+static jlong NativeOpenNonAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie,
+ jstring asset_path, jint access_mode) {
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
+ access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
+ return 0;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset;
+ if (cookie != kInvalidCookie) {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie,
+ static_cast<Asset::AccessMode>(access_mode));
+ } else {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(),
+ static_cast<Asset::AccessMode>(access_mode));
+ }
+
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(asset.release());
+}
+
+static jobject NativeOpenNonAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie,
+ jstring asset_path, jlongArray out_offsets) {
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return nullptr;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset;
+ if (cookie != kInvalidCookie) {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
+ } else {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
+ }
+
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return nullptr;
+ }
+ return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets);
+}
+
+static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie,
+ jstring asset_path) {
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset;
+ if (cookie != kInvalidCookie) {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
+ } else {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie);
+ }
+
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return 0;
+ }
+
+ // May be nullptr.
+ const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
+static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+ jshort density, jobject typed_value,
+ jboolean resolve_references) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ ApkAssetsCookie cookie =
+ assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
+ static_cast<uint16_t>(density), &value, &selected_config, &flags);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ uint32_t ref = static_cast<uint32_t>(resid);
+ if (resolve_references) {
+ cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+ }
+ return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
+}
+
+static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+ jint bag_entry_id, jobject typed_value) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ uint32_t type_spec_flags = bag->type_spec_flags;
+ ApkAssetsCookie cookie = kInvalidCookie;
+ const Res_value* bag_value = nullptr;
+ for (const ResolvedBag::Entry& entry : bag) {
+ if (entry.key == static_cast<uint32_t>(bag_entry_id)) {
+ cookie = entry.cookie;
+ bag_value = &entry.value;
+
+ // Keep searching (the old implementation did that).
+ }
+ }
+
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ Res_value value = *bag_value;
+ uint32_t ref = static_cast<uint32_t>(resid);
+ ResTable_config selected_config;
+ cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &type_spec_flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+ return CopyValue(env, cookie, value, ref, type_spec_flags, nullptr, typed_value);
+}
+
+static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jintArray array = env->NewIntArray(bag->entry_count);
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ for (uint32_t i = 0; i < bag->entry_count; i++) {
+ jint attr_resid = bag->entries[i].key;
+ env->SetIntArrayRegion(array, i, 1, &attr_resid);
+ }
+ return array;
+}
+
+static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jobjectArray array = env->NewObjectArray(bag->entry_count, g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ for (uint32_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+
+ // Resolve any references to their final value.
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return nullptr;
+ }
+
+ if (value.dataType == Res_value::TYPE_STRING) {
+ const ApkAssets* apk_assets = assetmanager->GetApkAssets()[cookie];
+ const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool();
+
+ jstring java_string = nullptr;
+ size_t str_len;
+ const char* str_utf8 = pool->string8At(value.data, &str_len);
+ if (str_utf8 != nullptr) {
+ java_string = env->NewStringUTF(str_utf8);
+ } else {
+ const char16_t* str_utf16 = pool->stringAt(value.data, &str_len);
+ java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16), str_len);
+ }
+
+ // Check for errors creating the strings (if malformed or no memory).
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(array, i, java_string);
+
+ // If we have a large amount of string in our array, we might overflow the
+ // local reference table of the VM.
+ env->DeleteLocalRef(java_string);
+ }
+ }
+ return array;
+}
+
+static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jintArray array = env->NewIntArray(bag->entry_count * 2);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr));
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT);
+ return nullptr;
+ }
+
+ jint string_index = -1;
+ if (value.dataType == Res_value::TYPE_STRING) {
+ string_index = static_cast<jint>(value.data);
+ }
+
+ buffer[i * 2] = ApkAssetsCookieToJavaCookie(cookie);
+ buffer[(i * 2) + 1] = string_index;
+ }
+ env->ReleasePrimitiveArrayCritical(array, buffer, 0);
+ return array;
+}
+
+static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jintArray array = env->NewIntArray(bag->entry_count);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr));
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT);
+ return nullptr;
+ }
+
+ if (value.dataType >= Res_value::TYPE_FIRST_INT && value.dataType <= Res_value::TYPE_LAST_INT) {
+ buffer[i] = static_cast<jint>(value.data);
+ }
+ }
+ env->ReleasePrimitiveArrayCritical(array, buffer, 0);
+ return array;
+}
+
+static jint NativeGetResourceArraySize(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return -1;
+ }
+ return static_cast<jint>(bag->entry_count);
+}
+
+static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+ jintArray out_data) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return -1;
+ }
+
+ const jsize out_data_length = env->GetArrayLength(out_data);
+ if (env->ExceptionCheck()) {
+ return -1;
+ }
+
+ if (static_cast<jsize>(bag->entry_count) > out_data_length * STYLE_NUM_ENTRIES) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Input array is not large enough");
+ return -1;
+ }
+
+ jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_data, nullptr));
+ if (buffer == nullptr) {
+ return -1;
+ }
+
+ jint* cursor = buffer;
+ for (size_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ selected_config.density = 0;
+ uint32_t flags = bag->type_spec_flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ env->ReleasePrimitiveArrayCritical(out_data, buffer, JNI_ABORT);
+ return -1;
+ }
+
+ // Deal with the special @null value -- it turns back to TYPE_NULL.
+ if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ }
+
+ cursor[STYLE_TYPE] = static_cast<jint>(value.dataType);
+ cursor[STYLE_DATA] = static_cast<jint>(value.data);
+ cursor[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
+ cursor[STYLE_RESOURCE_ID] = static_cast<jint>(ref);
+ cursor[STYLE_CHANGING_CONFIGURATIONS] = static_cast<jint>(flags);
+ cursor[STYLE_DENSITY] = static_cast<jint>(selected_config.density);
+ cursor += STYLE_NUM_ENTRIES;
+ }
+ env->ReleasePrimitiveArrayCritical(out_data, buffer, 0);
+ return static_cast<jint>(bag->entry_count);
+}
+
+static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name,
+ jstring def_type, jstring def_package) {
+ ScopedUtfChars name_utf8(env, name);
+ if (name_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ std::string type;
+ if (def_type != nullptr) {
+ ScopedUtfChars type_utf8(env, def_type);
+ CHECK(type_utf8.c_str() != nullptr);
+ type = type_utf8.c_str();
+ }
+
+ std::string package;
+ if (def_package != nullptr) {
+ ScopedUtfChars package_utf8(env, def_package);
+ CHECK(package_utf8.c_str() != nullptr);
+ package = package_utf8.c_str();
+ }
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ return static_cast<jint>(assetmanager->GetResourceId(name_utf8.c_str(), type, package));
+}
+
+static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ std::string result;
+ if (name.package != nullptr) {
+ result.append(name.package, name.package_len);
+ }
+
+ if (name.type != nullptr || name.type16 != nullptr) {
+ if (!result.empty()) {
+ result += ":";
+ }
+
+ if (name.type != nullptr) {
+ result.append(name.type, name.type_len);
+ } else {
+ result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len));
+ }
+ }
+
+ if (name.entry != nullptr || name.entry16 != nullptr) {
+ if (!result.empty()) {
+ result += "/";
+ }
+
+ if (name.entry != nullptr) {
+ result.append(name.entry, name.entry_len);
+ } else {
+ result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len));
+ }
+ }
+ return env->NewStringUTF(result.c_str());
+}
+
+static jstring NativeGetResourcePackageName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ if (name.package != nullptr) {
+ return env->NewStringUTF(name.package);
+ }
+ return nullptr;
+}
+
+static jstring NativeGetResourceTypeName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ if (name.type != nullptr) {
+ return env->NewStringUTF(name.type);
+ } else if (name.type16 != nullptr) {
+ return env->NewString(reinterpret_cast<const jchar*>(name.type16), name.type_len);
+ }
+ return nullptr;
+}
+
+static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ if (name.entry != nullptr) {
+ return env->NewStringUTF(name.entry);
+ } else if (name.entry16 != nullptr) {
+ return env->NewString(reinterpret_cast<const jchar*>(name.entry16), name.entry_len);
+ }
+ return nullptr;
+}
+
+static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr,
+ jboolean exclude_system) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::set<std::string> locales =
+ assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/);
+
+ jobjectArray array = env->NewObjectArray(locales.size(), g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ size_t idx = 0;
+ for (const std::string& locale : locales) {
+ jstring java_string = env->NewStringUTF(locale.c_str());
+ if (java_string == nullptr) {
+ return nullptr;
+ }
+ env->SetObjectArrayElement(array, idx++, java_string);
+ env->DeleteLocalRef(java_string);
+ }
+ return array;
+}
+
+static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
+ jobject result =
+ env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor);
+ if (result == nullptr) {
+ return nullptr;
+ }
+
+ env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset,
+ config.smallestScreenWidthDp);
+ env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
+ env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
+ return result;
+}
+
+static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::set<ResTable_config> configurations =
+ assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/);
+
+ jobjectArray array =
+ env->NewObjectArray(configurations.size(), gConfigurationOffsets.classObject, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ size_t idx = 0;
+ for (const ResTable_config& configuration : configurations) {
+ jobject java_configuration = ConstructConfigurationObject(env, configuration);
+ if (java_configuration == nullptr) {
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(array, idx++, java_configuration);
+ env->DeleteLocalRef(java_configuration);
+ }
+ return array;
+}
+
+static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
+ jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
+ uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr);
+ uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr);
+
+ jsize attrs_len = env->GetArrayLength(java_attrs);
+ jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+ if (attrs == nullptr) {
+ return;
+ }
+
+ ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr),
+ static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len,
+ out_values, out_indices);
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+}
+
+static jboolean NativeResolveAttrs(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint def_style_attr, jint def_style_resid, jintArray java_values,
+ jintArray java_attrs, jintArray out_java_values,
+ jintArray out_java_indices) {
+ const jsize attrs_len = env->GetArrayLength(java_attrs);
+ const jsize out_values_len = env->GetArrayLength(out_java_values);
+ if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) {
+ jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small");
+ return JNI_FALSE;
+ }
+
+ jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+ if (attrs == nullptr) {
+ return JNI_FALSE;
+ }
+
+ jint* values = nullptr;
+ jsize values_len = 0;
+ if (java_values != nullptr) {
+ values_len = env->GetArrayLength(java_values);
+ values = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_values, nullptr));
+ if (values == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return JNI_FALSE;
+ }
+ }
+
+ jint* out_values =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr));
+ if (out_values == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ if (values != nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+ }
+ return JNI_FALSE;
+ }
+
+ jint* out_indices = nullptr;
+ if (out_java_indices != nullptr) {
+ jsize out_indices_len = env->GetArrayLength(out_java_indices);
+ if (out_indices_len > attrs_len) {
+ out_indices =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr));
+ if (out_indices == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ if (values != nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+ }
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT);
+ return JNI_FALSE;
+ }
+ }
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ bool result = ResolveAttrs(
+ theme, static_cast<uint32_t>(def_style_attr), static_cast<uint32_t>(def_style_resid),
+ reinterpret_cast<uint32_t*>(values), values_len, reinterpret_cast<uint32_t*>(attrs),
+ attrs_len, reinterpret_cast<uint32_t*>(out_values), reinterpret_cast<uint32_t*>(out_indices));
+ if (out_indices != nullptr) {
+ env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0);
+ }
+
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0);
+ if (values != nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+ }
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jlong xml_parser_ptr, jintArray java_attrs,
+ jintArray out_java_values, jintArray out_java_indices) {
+ const jsize attrs_len = env->GetArrayLength(java_attrs);
+ const jsize out_values_len = env->GetArrayLength(out_java_values);
+ if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) {
+ jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small");
+ return JNI_FALSE;
+ }
+
+ jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+ if (attrs == nullptr) {
+ return JNI_FALSE;
+ }
+
+ jint* out_values =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr));
+ if (out_values == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return JNI_FALSE;
+ }
+
+ jint* out_indices = nullptr;
+ if (out_java_indices != nullptr) {
+ jsize out_indices_len = env->GetArrayLength(out_java_indices);
+ if (out_indices_len > attrs_len) {
+ out_indices =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr));
+ if (out_indices == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT);
+ return JNI_FALSE;
+ }
+ }
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
+
+ bool result = RetrieveAttributes(assetmanager.get(), xml_parser,
+ reinterpret_cast<uint32_t*>(attrs), attrs_len,
+ reinterpret_cast<uint32_t*>(out_values),
+ reinterpret_cast<uint32_t*>(out_indices));
+
+ if (out_indices != nullptr) {
+ env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0);
+ }
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0);
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
+}
+
+static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+ delete reinterpret_cast<Theme*>(theme_ptr);
+}
+
+static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint resid, jboolean force) {
+ // AssetManager is accessed via the theme, so grab an explicit lock here.
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+ theme->ApplyStyle(static_cast<uint32_t>(resid), force);
+
+ // TODO(adamlesinski): Consider surfacing exception when result is failure.
+ // CTS currently expects no exceptions from this method.
+ // std::string error_msg = StringPrintf("Failed to apply style 0x%08x to theme", resid);
+ // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
+}
+
+static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr,
+ jlong src_theme_ptr) {
+ Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr);
+ Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr);
+ if (!dst_theme->SetTo(*src_theme)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Themes are from different AssetManagers");
+ }
+}
+
+static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+ reinterpret_cast<Theme*>(theme_ptr)->Clear();
+}
+
+static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint resid, jobject typed_value,
+ jboolean resolve_references) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie = theme->GetAttribute(static_cast<uint32_t>(resid), &value, &flags);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ uint32_t ref = 0u;
+ if (resolve_references) {
+ ResTable_config selected_config;
+ cookie =
+ theme->GetAssetManager()->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+ }
+ return CopyValue(env, cookie, value, ref, flags, nullptr, typed_value);
+}
+
+static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint priority, jstring tag, jstring prefix) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+ (void) theme;
+ (void) priority;
+ (void) tag;
+ (void) prefix;
+}
+
+static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/,
+ jlong theme_ptr) {
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ return static_cast<jint>(theme->GetChangingConfigurations());
+}
+
+static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ delete reinterpret_cast<Asset*>(asset_ptr);
+}
+
+static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ uint8_t b;
+ ssize_t res = asset->read(&b, sizeof(b));
+ return res == sizeof(b) ? static_cast<jint>(b) : -1;
+}
+
+static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer,
+ jint offset, jint len) {
+ if (len == 0) {
+ return 0;
+ }
+
+ jsize buffer_len = env->GetArrayLength(java_buffer);
+ if (offset < 0 || offset >= buffer_len || len < 0 || len > buffer_len ||
+ offset > buffer_len - len) {
+ jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
+ return -1;
+ }
+
+ ScopedByteArrayRW byte_array(env, java_buffer);
+ if (byte_array.get() == nullptr) {
+ return -1;
+ }
+
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ ssize_t res = asset->read(byte_array.get() + offset, len);
+ if (res < 0) {
+ jniThrowException(env, "java/io/IOException", "");
+ return -1;
+ }
+ return res > 0 ? static_cast<jint>(res) : -1;
+}
+
+static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset,
+ jint whence) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ return static_cast<jlong>(asset->seek(
+ static_cast<off64_t>(offset), (whence > 0 ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR))));
+}
+
+static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ return static_cast<jlong>(asset->getLength());
+}
+
+static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ return static_cast<jlong>(asset->getRemainingLength());
+}
+
+// ----------------------------------------------------------------------------
+
+// JNI registration.
+static const JNINativeMethod gAssetManagerMethods[] = {
+ // AssetManager setup methods.
+ {"nativeCreate", "()J", (void*)NativeCreate},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+ {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V",
+ (void*)NativeSetConfiguration},
+ {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;",
+ (void*)NativeGetAssignedPackageIdentifiers},
+
+ // AssetManager file methods.
+ {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+ {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+ {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenAssetFd},
+ {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+ {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenNonAssetFd},
+ {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+
+ // AssetManager resource methods.
+ {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
+ {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+ (void*)NativeGetResourceBagValue},
+ {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+ {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+ (void*)NativeGetResourceStringArray},
+ {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+ {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+ {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+ {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+
+ // AssetManager resource name/ID methods.
+ {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)NativeGetResourceIdentifier},
+ {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+ {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
+ {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+ {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+ {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+ {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeConfigurations},
+
+ // Style attribute related methods.
+ {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+ {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+ {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+
+ // Theme related methods.
+ {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+ {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy},
+ {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+ {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy},
+ {"nativeThemeClear", "(J)V", (void*)NativeThemeClear},
+ {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+ (void*)NativeThemeGetAttributeValue},
+ {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+ {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
+
+ // AssetInputStream methods.
+ {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+ {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+ {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+ {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+ {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+ {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+
+ // System/idmap related methods.
+ {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps},
+
+ // Global management/debug methods.
+ {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+ {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+ {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+};
+
+int register_android_content_AssetManager(JNIEnv* env) {
+ jclass apk_assets_class = FindClassOrDie(env, "android/content/res/ApkAssets");
+ gApkAssetsFields.native_ptr = GetFieldIDOrDie(env, apk_assets_class, "mNativePtr", "J");
+
+ jclass typedValue = FindClassOrDie(env, "android/util/TypedValue");
+ gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I");
+ gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I");
+ gTypedValueOffsets.mString =
+ GetFieldIDOrDie(env, typedValue, "string", "Ljava/lang/CharSequence;");
+ gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I");
+ gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I");
+ gTypedValueOffsets.mChangingConfigurations =
+ GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I");
+ gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+
+ jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
+ gAssetFileDescriptorOffsets.mFd =
+ GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
+ gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
+ gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+
+ jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
+ gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
+
+ jclass stringClass = FindClassOrDie(env, "java/lang/String");
+ g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+ jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray");
+ gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass);
+ gSparseArrayOffsets.constructor =
+ GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "<init>", "()V");
+ gSparseArrayOffsets.put =
+ GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V");
+
+ jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration");
+ gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass);
+ gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, "<init>", "()V");
+ gConfigurationOffsets.mSmallestScreenWidthDpOffset =
+ GetFieldIDOrDie(env, configurationClass, "smallestScreenWidthDp", "I");
+ gConfigurationOffsets.mScreenWidthDpOffset =
+ GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I");
+ gConfigurationOffsets.mScreenHeightDpOffset =
+ GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
+
+ return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
+ NELEM(gAssetManagerMethods));
}
}; // namespace android
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 1350f3f..5b788a6 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -673,7 +673,7 @@
nativeData->mObject = val;
gNativeDataCache = nullptr;
++gNumProxies;
- if (++gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) {
+ if (gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) {
ALOGW("Unexpectedly many live BinderProxies: %d\n", gNumProxies);
gProxiesWarned = gNumProxies;
}
diff --git a/core/jni/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h
index 8dd9337..2c1e357 100644
--- a/core/jni/include/android_runtime/android_util_AssetManager.h
+++ b/core/jni/include/android_runtime/android_util_AssetManager.h
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-#ifndef android_util_AssetManager_H
-#define android_util_AssetManager_H
+#ifndef ANDROID_RUNTIME_ASSETMANAGER_H
+#define ANDROID_RUNTIME_ASSETMANAGER_H
-#include <androidfw/AssetManager.h>
+#include "androidfw/AssetManager2.h"
+#include "androidfw/MutexGuard.h"
#include "jni.h"
namespace android {
-extern AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject assetMgr);
+extern AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager);
+extern Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager);
+extern Guarded<AssetManager2>* AssetManagerForNdkAssetManager(AAssetManager* assetmanager);
-}
+} // namespace android
-#endif
+#endif // ANDROID_RUNTIME_ASSETMANAGER_H
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 0b0ed83..698f394 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -204,7 +204,10 @@
(section).args = "power --proto"
];
- optional android.service.print.PrintServiceDumpProto print = 3010;
+ optional android.service.print.PrintServiceDumpProto print = 3010 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "print --proto"
+ ];
optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index 0342c9c..d1c5db6 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -104,11 +104,6 @@
repeated InFlightProto outstanding_deliveries = 34;
- // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It
- // should be either CosntantsProto.allow_while_idle_short_duration_ms or
- // ConstantsProto.allow_while_idle_long_duration_ms.
- optional int64 allow_while_idle_min_duration_ms = 35;
-
message LastAllowWhileIdleDispatch {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -121,7 +116,7 @@
}
// Whether the short or long while-idle timeout should be used for each UID.
- repeated int32 use_allow_while_idle_short_time = 42;
+ repeated int32 use_allow_while_idle_short_time = 35;
// For each uid, this is the last time we dispatched an "allow while idle"
// alarm, used to determine the earliest we can dispatch the next such alarm.
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index b5c3ac0..5cb5319 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -227,7 +227,6 @@
optional int32 setting_minimum = 1;
optional int32 setting_maximum = 2;
optional int32 setting_default = 3;
- optional int32 setting_for_vr_default = 4;
}
// True to decouple auto-suspend mode from the display state.
@@ -293,44 +292,27 @@
// The stay on while plugged in setting.
// A set of battery conditions under which to make the screen stay on.
optional StayOnWhilePluggedInProto stay_on_while_plugged_in = 29;
- // The screen brightness setting, from 0 to 255.
- // Use -1 if no value has been set.
- optional sint32 screen_brightness_setting = 30;
- // The screen auto-brightness adjustment setting, from -1 to 1.
- // Use 0 if there is no adjustment.
- optional float screen_auto_brightness_adjustment_setting = 31;
// The screen brightness mode.
- optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 32;
+ optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 30;
// The screen brightness setting override from the window manager
// to allow the current foreground activity to override the brightness.
// Use -1 to disable.
- optional sint32 screen_brightness_override_from_window_manager = 33;
+ optional sint32 screen_brightness_override_from_window_manager = 31;
// The user activity timeout override from the window manager
// to allow the current foreground activity to override the user activity
// timeout. Use -1 to disable.
- optional sint64 user_activity_timeout_override_from_window_manager_ms = 34;
+ optional sint64 user_activity_timeout_override_from_window_manager_ms = 32;
// The window manager has determined the user to be inactive via other means.
// Set this to false to disable.
- optional bool is_user_inactive_override_from_window_manager = 35;
- // The screen brightness setting override from the settings application
- // to temporarily adjust the brightness until next updated,
- // Use -1 to disable.
- optional sint32 temporary_screen_brightness_setting_override = 36;
- // The screen brightness adjustment setting override from the settings
- // application to temporarily adjust the auto-brightness adjustment factor
- // until next updated, in the range -1..1.
- // Use NaN to disable.
- optional float temporary_screen_auto_brightness_adjustment_setting_override = 37;
+ optional bool is_user_inactive_override_from_window_manager = 33;
// The screen state to use while dozing.
- optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 38;
+ optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 34;
// The screen brightness to use while dozing.
- optional float dozed_screen_brightness_override_from_dream_manager = 39;
+ optional float dozed_screen_brightness_override_from_dream_manager = 35;
// Screen brightness settings limits.
- optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 40;
- // The screen brightness setting, from 0 to 255, to be used while in VR Mode.
- optional int32 screen_brightness_for_vr_setting = 41;
+ optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 36;
// True if double tap to wake is enabled
- optional bool is_double_tap_wake_enabled = 42;
+ optional bool is_double_tap_wake_enabled = 37;
// True if we are currently in VR Mode.
- optional bool is_vr_mode_enabled = 43;
+ optional bool is_vr_mode_enabled = 38;
}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 449e546..c11058a 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -295,6 +295,7 @@
optional bool animating_exit = 14;
repeated WindowStateProto child_windows = 15;
optional .android.graphics.RectProto surface_position = 16;
+ optional .android.graphics.RectProto shown_position = 17;
optional int32 requested_width = 18;
optional int32 requested_height = 19;
optional int32 view_visibility = 20;
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index ef777de..f8050a1 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -49,7 +49,8 @@
option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 user_id = 1;
- optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
+ // Name of the shared UID. eg: android.uid.bluetooth
+ optional string name = 2;
}
// Installed packages.
diff --git a/core/proto/android/service/print.proto b/core/proto/android/service/print.proto
index c2be7f1..f783b86 100644
--- a/core/proto/android/service/print.proto
+++ b/core/proto/android/service/print.proto
@@ -21,13 +21,18 @@
option java_outer_classname = "PrintServiceProto";
import "frameworks/base/core/proto/android/content/component_name.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message PrintServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Each user has a separate printer state
repeated PrintUserStateProto userStates = 1;
}
message PrintUserStateProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Should be 0, 10, 11, 12, etc. where 0 is the owner.
optional int32 user_id = 1;
@@ -51,6 +56,8 @@
}
message PrintSpoolerStateProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Is the print spooler destroyed?
optional bool is_destroyed = 1;
@@ -62,6 +69,8 @@
}
message PrintSpoolerInternalStateProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Print jobs
repeated PrintJobInfoProto print_jobs = 1;
@@ -73,6 +82,8 @@
}
message PrinterCapabilitiesProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Minimum margins of the printer
optional MarginsProto min_margins = 1;
@@ -90,6 +101,8 @@
}
message PrinterInfoProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// The id of the printer
optional PrinterIdProto id = 1;
@@ -120,6 +133,8 @@
}
message PrinterDiscoverySessionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Is this session destroyed?
optional bool is_destroyed = 1;
@@ -140,6 +155,8 @@
}
message InstalledPrintServiceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Component name of the service
optional android.content.ComponentNameProto component_name = 1;
@@ -154,14 +171,18 @@
}
message PrinterIdProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Component name of the service that reported the printer
optional android.content.ComponentNameProto service_name = 1;
// Local id of the printer
- optional string local_id = 2;
+ optional string local_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
}
message ActivePrintServiceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Component name of the service
optional android.content.ComponentNameProto component_name = 1;
@@ -185,6 +206,8 @@
}
message MediaSizeProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Id of this media size
optional string id = 1;
@@ -199,6 +222,8 @@
}
message ResolutionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Id of this resolution
optional string id = 1;
@@ -213,6 +238,8 @@
}
message MarginsProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Space at the top
optional int32 top_mils = 1;
@@ -227,6 +254,8 @@
}
message PrintAttributesProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Media to use
optional ResolutionProto media_size = 1;
@@ -270,8 +299,10 @@
}
message PrintDocumentInfoProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Name of the document to print
- optional string name = 1;
+ optional string name = 1 [ (android.privacy).dest = DEST_EXPLICIT ];
// Number of pages in the doc
optional int32 page_count = 2;
@@ -284,6 +315,8 @@
}
message PageRangeProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Start of the range
optional int32 start = 1;
@@ -292,8 +325,10 @@
}
message PrintJobInfoProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Label of the job
- optional string label = 1;
+ optional string label = 1 [ (android.privacy).dest = DEST_EXPLICIT ];
// Id of the job
optional string print_job_id = 2;
@@ -359,6 +394,8 @@
}
message CachedPrintJobProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// The id of the app the job belongs to
optional int32 app_id = 1;
diff --git a/core/proto/android/telecomm/enums.proto b/core/proto/android/telecomm/enums.proto
new file mode 100644
index 0000000..7a2ba62
--- /dev/null
+++ b/core/proto/android/telecomm/enums.proto
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.telecom;
+
+option java_outer_classname = "TelecomProtoEnums";
+option java_multiple_files = true;
+
+/**
+ * Call states, primarily used in CallState.java,
+ * Call.java, and CallsManager.java in packages/services.
+ */
+enum CallStateEnum {
+ /**
+ * Indicates that a call is new and not connected. This is used as the default state internally
+ * within Telecom and should not be used between Telecom and call services. Call services are
+ * not expected to ever interact with NEW calls, but {@link android.telecom.InCallService}s will
+ * see calls in this state.
+ */
+ NEW = 0;
+
+ /**
+ * The initial state of an outgoing {@code Call}.
+ * Common transitions are to {@link #DIALING} state for a successful call or
+ * {@link #DISCONNECTED} if it failed.
+ */
+ CONNECTING = 1;
+
+ /**
+ * The state of an outgoing {@code Call} when waiting on user to select a
+ * {@link android.telecom.PhoneAccount} through which to place the call.
+ */
+ SELECT_PHONE_ACCOUNT = 2;
+
+ /**
+ * Indicates that a call is outgoing and in the dialing state. A call transitions to this state
+ * once an outgoing call has begun (e.g., user presses the dial button in Dialer). Calls in this
+ * state usually transition to {@link #ACTIVE} if the call was answered or {@link #DISCONNECTED}
+ * if the call was disconnected somehow (e.g., failure or cancellation of the call by the user).
+ */
+ DIALING = 3;
+
+ /**
+ * Indicates that a call is incoming and the user still has the option of answering, rejecting,
+ * or doing nothing with the call. This state is usually associated with some type of audible
+ * ringtone. Normal transitions are to {@link #ACTIVE} if answered or {@link #DISCONNECTED}
+ * otherwise.
+ */
+ RINGING = 4;
+
+ /**
+ * Indicates that a call is currently connected to another party and a communication channel is
+ * open between them. The normal transition to this state is by the user answering a
+ * {@link #DIALING} call or a {@link #RINGING} call being answered by the other party.
+ */
+ ACTIVE = 5;
+
+ /**
+ * Indicates that the call is currently on hold. In this state, the call is not terminated
+ * but no communication is allowed until the call is no longer on hold. The typical transition
+ * to this state is by the user putting an {@link #ACTIVE} call on hold by explicitly performing
+ * an action, such as clicking the hold button.
+ */
+ ON_HOLD = 6;
+
+ /**
+ * Indicates that a call is currently disconnected. All states can transition to this state
+ * by the call service giving notice that the connection has been severed. When the user
+ * explicitly ends a call, it will not transition to this state until the call service confirms
+ * the disconnection or communication was lost to the call service currently responsible for
+ * this call (e.g., call service crashes).
+ */
+ DISCONNECTED = 7;
+
+ /**
+ * Indicates that the call was attempted (mostly in the context of outgoing, at least at the
+ * time of writing) but cancelled before it was successfully connected.
+ */
+ ABORTED = 8;
+
+ /**
+ * Indicates that the call is in the process of being disconnected and will transition next
+ * to a {@link #DISCONNECTED} state.
+ * <p>
+ * This state is not expected to be communicated from the Telephony layer, but will be reported
+ * to the InCall UI for calls where disconnection has been initiated by the user but the
+ * ConnectionService has confirmed the call as disconnected.
+ */
+ DISCONNECTING = 9;
+
+ /**
+ * Indicates that the call is in the process of being pulled to the local device.
+ * <p>
+ * This state should only be set on a call with
+ * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} and
+ * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL}.
+ */
+ PULLING = 10;
+}
+
+// Disconnect causes for a call. Primarily used by android/telecom/DisconnectCause.java
+enum DisconnectCauseEnum {
+ /**
+ * Disconnected because of an unknown or unspecified reason.
+ */
+ UNKNOWN = 0;
+
+ /**
+ * Disconnected because there was an error, such as a problem with the network.
+ */
+ ERROR = 1;
+
+ /**
+ * Disconnected because of a local user-initiated action, such as hanging up.
+ */
+ LOCAL = 2;
+
+ /**
+ * Disconnected because of a remote user-initiated action, such as the other party hanging up
+ * up.
+ */
+ REMOTE = 3;
+
+ /**
+ * Disconnected because it has been canceled.
+ */
+ CANCELED = 4;
+
+ /**
+ * Disconnected because there was no response to an incoming call.
+ */
+ MISSED = 5;
+
+ /**
+ * Disconnected because the user rejected an incoming call.
+ */
+ REJECTED = 6;
+
+ /**
+ * Disconnected because the other party was busy.
+ */
+ BUSY = 7;
+
+ /**
+ * Disconnected because of a restriction on placing the call, such as dialing in airplane
+ * mode.
+ */
+ RESTRICTED = 8;
+
+ /**
+ * Disconnected for reason not described by other disconnect codes.
+ */
+ OTHER = 9;
+
+ /**
+ * Disconnected because the connection manager did not support the call. The call will be tried
+ * again without a connection manager. See {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
+ */
+ CONNECTION_MANAGER_NOT_SUPPORTED = 10;
+
+ /**
+ * Disconnected because the user did not locally answer the incoming call, but it was answered
+ * on another device where the call was ringing.
+ */
+ ANSWERED_ELSEWHERE = 11;
+
+ /**
+ * Disconnected because the call was pulled from the current device to another device.
+ */
+ CALL_PULLED = 12;
+}
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 3ac8f3b..cbd06fd 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -29,4 +29,5 @@
optional int32 logical_height = 2;
optional int32 app_width = 3;
optional int32 app_height = 4;
+ optional string name = 5;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f627aaa..f6f1d81 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -53,6 +53,7 @@
<protected-broadcast android:name="android.intent.action.UID_REMOVED" />
<protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
<protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.SPLIT_CONFIGURATION_CHANGED" />
<protected-broadcast android:name="android.intent.action.LOCALE_CHANGED" />
<protected-broadcast android:name="android.intent.action.BATTERY_CHANGED" />
<protected-broadcast android:name="android.intent.action.BATTERY_LOW" />
@@ -3784,6 +3785,15 @@
<permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
android:protectionLevel="signature|development|instant|appop" />
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE"
+ android:description="@string/permdesc_foregroundService"
+ android:label="@string/permlab_foregroundService"
+ android:protectionLevel="normal|instant" />
+
<!-- @hide Allows system components to access all app shortcuts. -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
android:protectionLevel="signature" />
@@ -3861,6 +3871,7 @@
android:excludeFromRecents="true"
android:label="@string/user_owner_label"
android:exported="true"
+ android:visibleToInstantApps="true"
>
</activity>
<activity-alias android:name="com.android.internal.app.ForwardIntentToParent"
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index a6dee8a..f5bbadc 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2017 The Android Open Source Project
+Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,36 +13,21 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
- android:viewportWidth="48"
- android:viewportHeight="48">
- <group>
- <path
- android:fillColor="#2C292A"
- android:fillType="evenOdd"
- android:pathData="M6,26a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/>
- <path
- android:fillColor="#FAFAFA"
- android:fillType="evenOdd"
- android:pathData="M4,24a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/>
- <path
- android:fillColor="#2C292A"
- android:fillType="evenOdd"
- android:pathData="M2,22a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/>
- <path
- android:fillColor="#00000000"
- android:strokeColor="#453F41"
- android:strokeWidth="1"
- android:fillType="evenOdd"
- android:pathData="M26.5 29.5v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3h-1v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3H17a1.5 1.5 0 0 1-1.5-1.5V17.5h13V28a1.5 1.5 0 0 1-1.5 1.5h-.5zM13.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM30.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM26.3 12.11A6.46 6.46 0 0 1 28.5 17v.5h-13V17a6.46 6.46 0 0 1 2.2-4.89l-.9-.9a.98.98 0 0 1 0-1.41.98.98 0 0 1 1.4 0l1.26 1.25A6.33 6.33 0 0 1 22 10.5c.87 0 1.73.2 2.54.55L25.8 9.8a.98.98 0 0 1 1.4 0 .98.98 0 0 1 0 1.4l-.9.91z"/>
- <path
- android:fillColor="#453F41"
- android:fillType="evenOdd"
- android:pathData="M20.16 14.5a.66.66 0 1 1-1.31 0c0-.36.29-.65.65-.65.36 0 .65.29.65.65zM25.16 14.5c0 .36-.3.66-.66.66a.65.65 0 1 1 .66-.66z"/>
- <path
- android:fillColor="#453F41"
- android:pathData="M22 40.5c0.36 0 0.73-0.01 1.09-0.03l-0.18-3A15.77 15.77 0 0 1 22 37.5v3zm2.17-0.13a18.48 18.48 0 0 0 1.08-0.15l-0.53-2.96c-0.3 0.05-0.6 0.1-0.9 0.13l0.35 2.98zM26.32 40a18.37 18.37 0 0 0 1.05-0.28l-0.87-2.87a15.37 15.37 0 0 1-0.88 0.23l0.7 2.92zm2.1-0.64l-1.03-2.81a15.39 15.39 0 0 0 0.84-0.34l1.2 2.74a18.39 18.39 0 0 1-1 0.41zm1.99-0.87l-1.37-2.67a15.46 15.46 0 0 0 0.8-0.44l1.52 2.59a18.46 18.46 0 0 1-0.95 0.52zm1.89-1.11l-1.67-2.5a15.55 15.55 0 0 0 0.74-0.52l1.81 2.39a18.55 18.55 0 0 1-0.88 0.63zm1.75-1.33l-1.95-2.28a15.6 15.6 0 0 0 0.67-0.61l2.09 2.15a18.6 18.6 0 0 1-0.8 0.74zm1.6-1.55l-2.22-2.02a15.6 15.6 0 0 0 0.6-0.7l2.33 1.9a18.6 18.6 0 0 1-0.72 0.82zM37 32.82l-2.43-1.76a15.53 15.53 0 0 0 0.5-0.75l2.54 1.6c-0.2 0.31-0.4 0.61-0.61 0.9zm1.15-1.8l-2.62-1.47a15.45 15.45 0 0 0 0.42-0.8l2.7 1.3a18.45 18.45 0 0 1-0.5 0.97zm0.95-1.98l-2.77-1.14a15.38 15.38 0 0 0 0.32-0.86l2.84 0.98a18.38 18.38 0 0 1-0.39 1.02zm0.72-2.09c0.1-0.34 0.18-0.7 0.26-1.05l-2.93-0.63a15.38 15.38 0 0 1-0.22 0.88l2.89 0.8zm0.46-2.15a18.52 18.52 0 0 0 0.13-1.08l-2.99-0.28a15.52 15.52 0 0 1-0.1 0.9l2.96 0.46zm0.2-2.2a18.81 18.81 0 0 0 0-1.1l-3 0.08a16 16 0 0 1 0 0.92l3 0.1zm-0.06-2.2a18.54 18.54 0 0 0-0.12-1.07l-2.97 0.43c0.04 0.3 0.08 0.6 0.1 0.9l3-0.25zm-0.31-2.15a18.39 18.39 0 0 0-0.25-1.06l-2.9 0.78a15.39 15.39 0 0 1 0.21 0.89l2.94-0.6zm-0.57-2.12l-2.85 0.95a15.37 15.37 0 0 0-0.31-0.85l2.78-1.12a18.37 18.37 0 0 1 0.38 1.02zm-0.83-2.06l-2.71 1.29a15.44 15.44 0 0 0-0.42-0.81l2.63-1.45a18.44 18.44 0 0 1 0.5 0.97zm-1.03-1.88l-2.54 1.6a15.53 15.53 0 0 0-0.5-0.76l2.44-1.74 0.6 0.9zm-1.28-1.79l-2.33 1.88a15.6 15.6 0 0 0-0.6-0.69l2.23-2.02a18.6 18.6 0 0 1 0.7 0.83zm-1.48-1.63l-2.1 2.14a15.6 15.6 0 0 0-0.67-0.62l1.97-2.26a18.6 18.6 0 0 1 0.8 0.74zM33.24 7.3l-1.82 2.38a15.55 15.55 0 0 0-0.74-0.53l1.68-2.49c0.3 0.2 0.6 0.42 0.88 0.64zm-1.71-1.17L29.98 8.7a15.47 15.47 0 0 0-0.8-0.45l1.4-2.66a18.47 18.47 0 0 1 0.95 0.54zm-1.95-1.02l-1.23 2.74A15.4 15.4 0 0 0 27.5 7.5l1.06-2.8a18.4 18.4 0 0 1 1.01 0.4zm-2.06-0.78l-0.9 2.86a15.37 15.37 0 0 0-0.87-0.24l0.72-2.92a18.37 18.37 0 0 1 1.05 0.3zM25.38 3.8a18.47 18.47 0 0 0-1.08-0.17l-0.37 2.98c0.3 0.04 0.6 0.08 0.9 0.14l0.55-2.95zm-2.2-0.27A18.75 18.75 0 0 0 22.1 3.5l-0.02 3L23 6.53l0.19-3zM21 3.53a18.6 18.6 0 0 0-1.08 0.09l0.33 2.98a15.6 15.6 0 0 1 0.91-0.08l-0.16-3zm-2.16 0.24A18.4 18.4 0 0 0 17.76 4l0.68 2.92a15.4 15.4 0 0 1 0.9-0.18l-0.51-2.96zm-2.14 0.5l0.86 2.88a15.37 15.37 0 0 0-0.86 0.28l-1.03-2.81a18.37 18.37 0 0 1 1.03-0.35zm-2.07 0.76l1.2 2.75a15.42 15.42 0 0 0-0.83 0.4L13.63 5.5a18.42 18.42 0 0 1 0.99-0.47zM12.7 6l1.5 2.6a15.5 15.5 0 0 0-0.76 0.48l-1.66-2.5A18.5 18.5 0 0 1 12.7 6zm-1.83 1.22l1.8 2.4a15.58 15.58 0 0 0-0.7 0.57L10.01 7.9a18.58 18.58 0 0 1 0.85-0.68zM9.19 8.66l2.07 2.16a15.6 15.6 0 0 0-0.63 0.65l-2.2-2.04a18.6 18.6 0 0 1 0.76-0.77zm-1.51 1.63l2.32 1.9a15.57 15.57 0 0 0-0.56 0.72l-2.42-1.76a18.57 18.57 0 0 1 0.66-0.86zm-1.23 1.69l2.52 1.62a15.5 15.5 0 0 0-0.47 0.78l-2.61-1.47a18.5 18.5 0 0 1 0.56-0.93zm-1.08 1.9l2.7 1.32a15.41 15.41 0 0 0-0.38 0.83l-2.77-1.15a18.41 18.41 0 0 1 0.45-1zm-0.85 2.04l2.84 0.98a15.37 15.37 0 0 0-0.28 0.87L4.2 16.96c0.1-0.35 0.2-0.7 0.32-1.04zm-0.6 2.12a18.43 18.43 0 0 0-0.2 1.07l2.97 0.47c0.05-0.3 0.1-0.6 0.17-0.9l-2.93-0.64zm-0.34 2.18a18.65 18.65 0 0 0-0.07 1.09l3 0.11 0.06-0.91-2.99-0.29zm-0.08 2.2a18.7 18.7 0 0 0 0.06 1.1l3-0.25a15.7 15.7 0 0 1-0.06-0.91l-3 0.07zm0.18 2.18a18.44 18.44 0 0 0 0.18 1.07l2.95-0.6a15.44 15.44 0 0 1-0.16-0.9L3.68 24.6zm0.43 2.14l2.9-0.77a15.37 15.37 0 0 0 0.26 0.88l-2.85 0.94a18.37 18.37 0 0 1-0.3-1.05zm0.7 2.1l2.78-1.11a15.4 15.4 0 0 0 0.36 0.83l-2.71 1.27a18.4 18.4 0 0 1-0.44-1zm0.9 1.95l2.65-1.43a15.48 15.48 0 0 0 0.45 0.8l-2.55 1.57a18.48 18.48 0 0 1-0.54-0.94zm1.17 1.87l2.45-1.73a15.56 15.56 0 0 0 0.54 0.73l-2.34 1.87a18.56 18.56 0 0 1-0.65-0.87zm1.37 1.72l2.23-2a15.6 15.6 0 0 0 0.63 0.65l-2.1 2.14a18.6 18.6 0 0 1-0.76-0.79zm1.58 1.56l1.98-2.26c0.22 0.2 0.46 0.39 0.7 0.58l-1.84 2.37a18.59 18.59 0 0 1-0.84-0.7zm1.66 1.28l1.7-2.46a15.52 15.52 0 0 0 0.77 0.5l-1.56 2.56a18.52 18.52 0 0 1-0.91-0.6zm1.87 1.14l1.4-2.65a15.43 15.43 0 0 0 0.82 0.4l-1.24 2.73a18.43 18.43 0 0 1-0.98-0.48zm2 0.91l1.08-2.8a15.37 15.37 0 0 0 0.86 0.3l-0.9 2.86a18.37 18.37 0 0 1-1.04-0.36zm2.1 0.67a18.4 18.4 0 0 0 1.07 0.23l0.56-2.94a15.4 15.4 0 0 1-0.9-0.2l-0.72 2.91zm2.18 0.41a18.57 18.57 0 0 0 1.08 0.1l0.2-2.99a15.57 15.57 0 0 1-0.9-0.09l-0.38 2.98zm2.2 0.15H22v-3h-0.13l-0.03 3z"/>
- </group>
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="vector"
+ android:width="640dp"
+ android:height="640dp"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+ <path
+ android:name="bg"
+ android:pathData="M 27 43 L 32 43 C 38.075 43 43 38.075 43 32 C 43 25.925 38.075 21 32 21 C 25.925 21 21 25.925 21 32 L 21 64"
+ android:strokeColor="#6823a1"
+ android:strokeWidth="16"/>
+ <path
+ android:name="fg"
+ android:pathData="M 29 43 L 32 43 C 38.075 43 43 38.075 43 32 C 43 25.925 38.075 21 32 21 C 25.925 21 21 25.925 21 32 L 21 64"
+ android:strokeColor="#ff0000"
+ android:strokeWidth="8"/>
</vector>
diff --git a/core/res/res/drawable-nodpi/platlogo_m.xml b/core/res/res/drawable-nodpi/platlogo_m.xml
index aacf674..8e43638 100644
--- a/core/res/res/drawable-nodpi/platlogo_m.xml
+++ b/core/res/res/drawable-nodpi/platlogo_m.xml
@@ -18,23 +18,4 @@
android:height="480dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
- <!--<path
- android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
- android:fillAlpha="0.066"
- android:fillColor="#000000"/>-->
- <path
- android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
- android:fillColor="#FFC107"/>
- <path
- android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
- android:fillColor="#FE9F00"/>
- <path
- android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
- android:fillColor="#FED44F"/>
- <path
- android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
- android:fillColor="#FFC107"/>
- <path
- android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
- android:fillColor="#FFFFFF"/>
</vector>
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 2e2b395..0fde2cc 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2017 The Android Open Source Project
+Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,24 +13,24 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <group>
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="vector"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <group android:name="stat_sys_adb">
<path
- android:fillColor="#FFFFFF"
- android:fillAlpha=".33"
- android:fillType="evenOdd"
- android:pathData="M5.71 18.29A8.99 8.99 0 0 0 22 13c0-3-1.46-5.65-3.71-7.29A8.99 8.99 0 0 0 2 11c0 3 1.46 5.65 3.71 7.29z"/>
+ android:name="outer"
+ android:pathData="M 18 30 L 24 30 C 29.523 30 34 25.523 34 20 C 34 14.477 29.523 10 24 10 C 18.477 10 14 14.477 14 20 L 14 44"
+ android:strokeColor="#000000"
+ android:strokeWidth="10"/>
<path
- android:fillColor="#FFFFFF"
- android:fillType="evenOdd"
- android:pathData="M7.25 19.18A8.5 8.5 0 0 0 19.19 7.24 9 9 0 0 1 7.24 19.19z"/>
- <path
- android:fillColor="#FFFFFF"
- android:fillAlpha=".33"
- android:pathData="M10.5 3a0.5 0.5 0 1 1 1 0v2.05a0.5 0.5 0 1 1-1 0V3zm3.1 0.42a0.5 0.5 0 0 1 0.93 0.39l-0.8 1.88A0.5 0.5 0 1 1 12.8 5.3l0.8-1.88zm2.7 1.57a0.5 0.5 0 1 1 0.71 0.7l-1.45 1.46a0.5 0.5 0 0 1-0.7-0.71l1.44-1.45zm1.9 2.5a0.5 0.5 0 0 1 0.38 0.92l-1.9 0.77a0.5 0.5 0 0 1-0.37-0.93l1.9-0.77zM19 10.5a0.5 0.5 0 1 1 0 1h-2.05a0.5 0.5 0 0 1 0-1H19zm-0.42 3.1a0.5 0.5 0 0 1-0.39 0.93l-1.88-0.8a0.5 0.5 0 1 1 0.39-0.92l1.88 0.8zm-1.57 2.7a0.5 0.5 0 1 1-0.7 0.71l-1.46-1.45a0.5 0.5 0 0 1 0.71-0.7l1.45 1.44zm-2.5 1.9a0.5 0.5 0 1 1-0.92 0.38l-0.77-1.9a0.5 0.5 0 0 1 0.93-0.37l0.77 1.9zM11.5 19a0.5 0.5 0 1 1-1 0v-2.05a0.5 0.5 0 0 1 1 0V19zm-3.1-0.42a0.5 0.5 0 0 1-0.93-0.39l0.8-1.88A0.5 0.5 0 0 1 9.2 16.7l-0.8 1.88zm-2.7-1.57a0.5 0.5 0 1 1-0.71-0.7l1.45-1.46a0.5 0.5 0 0 1 0.7 0.71L5.7 17.01zm-1.9-2.48a0.5 0.5 0 0 1-0.38-0.92l1.88-0.8a0.5 0.5 0 0 1 0.4 0.92l-1.9 0.8zM3 11.5a0.5 0.5 0 1 1 0-1h2.05a0.5 0.5 0 1 1 0 1H3zm0.42-3.1A0.5 0.5 0 0 1 3.8 7.46l1.88 0.8A0.5 0.5 0 1 1 5.3 9.2L3.42 8.4zm1.57-2.7a0.5 0.5 0 1 1 0.7-0.71l1.46 1.45a0.5 0.5 0 0 1-0.71 0.7L4.99 5.7zm2.5-1.9A0.5 0.5 0 0 1 8.4 3.41l0.77 1.9a0.5 0.5 0 0 1-0.93 0.37L7.48 3.8z"/>
+ android:name="inner"
+ android:pathData="M 19 30 L 24 30 C 29.523 30 34 25.523 34 20 C 34 14.477 29.523 10 24 10 C 18.477 10 14 14.477 14 20 L 14 44"
+ android:strokeColor="#000000"
+ android:strokeAlpha="0"
+ android:strokeWidth="6"/>
</group>
-</vector>
\ No newline at end of file
+</vector>
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
index 4ac308a..bd1030e 100644
--- a/core/res/res/layout/notification_template_messaging_group.xml
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -28,9 +28,9 @@
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/message_group_and_sender_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_weight="1"
android:orientation="vertical">
<com.android.internal.widget.ImageFloatingTextView
android:id="@+id/message_name"
@@ -44,4 +44,10 @@
android:spacing="2dp"
android:layout_weight="1"/>
</com.android.internal.widget.RemeasuringLinearLayout>
+ <FrameLayout
+ android:id="@+id/messaging_group_icon_container"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:layout_marginStart="12dp"
+ android:visibility="gone"/>
</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_image_message.xml
similarity index 71%
copy from core/res/res/layout/notification_template_messaging_message.xml
copy to core/res/res/layout/notification_template_messaging_image_message.xml
index ab6466c..6ca4dcd 100644
--- a/core/res/res/layout/notification_template_messaging_message.xml
+++ b/core/res/res/layout/notification_template_messaging_image_message.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2017 The Android Open Source Project
+ ~ Copyright (C) 2018 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,8 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.internal.widget.MessagingMessage
+<com.android.internal.widget.MessagingImageMessage
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/message_text"
- style="@style/Widget.Material.Notification.MessagingText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/messaging_image_extra_spacing"
+ android:scaleType="fitStart"
/>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_text_message.xml
similarity index 94%
rename from core/res/res/layout/notification_template_messaging_message.xml
rename to core/res/res/layout/notification_template_messaging_text_message.xml
index ab6466c..e728e69 100644
--- a/core/res/res/layout/notification_template_messaging_message.xml
+++ b/core/res/res/layout/notification_template_messaging_text_message.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.internal.widget.MessagingMessage
+<com.android.internal.widget.MessagingTextMessage
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/message_text"
style="@style/Widget.Material.Notification.MessagingText"
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index ce4ac61..e80f16c 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -79,8 +79,8 @@
<item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item>
<item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item>
- <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item>
- <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item>
+ <item name="highlight_alpha_material_dark" format="float" type="dimen">0.16</item>
+ <item name="highlight_alpha_material_colored" format="float" type="dimen">0.16</item>
<!-- Primary & accent colors -->
<eat-comment />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c8032a2..d2194ba 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1075,6 +1075,14 @@
<item>10</item>
</integer-array>
+ <!-- The URI to associate with each ringtone effect constant, intended to be used with the
+ android.os.VibrationEffect#get(Uri, Context) API.
+ The position of the string in the string-array determines which ringtone effect is chosen.
+ For example, if the URI passed into get match the third string in the string-array, then
+ RINGTONE_3 will be the returned effect -->
+ <string-array translatable="false" name="config_ringtoneEffectUris">
+ </string-array>
+
<bool name="config_use_strict_phone_number_comparation">false</bool>
<!-- Display low battery warning when battery level dips to this value.
@@ -2486,9 +2494,9 @@
<string-array translatable="false" name="config_globalActionsList">
<item>power</item>
<item>restart</item>
- <item>screenshot</item>
- <item>logout</item>
<item>lockdown</item>
+ <item>logout</item>
+ <item>screenshot</item>
<item>bugreport</item>
<item>users</item>
</string-array>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e610efd..0411c6e 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -262,6 +262,18 @@
<!-- The spacing between messages in Notification.MessagingStyle -->
<dimen name="notification_messaging_spacing">6dp</dimen>
+ <!-- The rounding for messaging images -->
+ <dimen name="messaging_image_rounding">4dp</dimen>
+
+ <!-- The minimum size for any image in messaging style in order to be displayed -->
+ <dimen name="messaging_image_min_size">44dp</dimen>
+
+ <!-- The maximum size for any image in messaging style in order to be displayed -->
+ <dimen name="messaging_image_max_height">136dp</dimen>
+
+ <!-- Extra spacing before and after images in messaging style -->
+ <dimen name="messaging_image_extra_spacing">8dp</dimen>
+
<!-- Preferred width and height of the search view. -->
<dimen name="search_view_preferred_width">320dip</dimen>
<dimen name="search_view_preferred_height">48dip</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ec81df7..2b7b056 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -916,6 +916,11 @@
<string name="permdesc_persistentActivity" product="default">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundService">run foreground service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_getPackageSize">measure app storage space</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 29b09b5..ca698ef 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3168,7 +3168,8 @@
<java-symbol type="dimen" name="chooser_service_spacing" />
<java-symbol type="bool" name="config_showSysuiShutdown" />
- <java-symbol type="layout" name="notification_template_messaging_message" />
+ <java-symbol type="layout" name="notification_template_messaging_text_message" />
+ <java-symbol type="layout" name="notification_template_messaging_image_message" />
<java-symbol type="layout" name="notification_template_messaging_group" />
<java-symbol type="id" name="message_text" />
<java-symbol type="id" name="message_name" />
@@ -3183,6 +3184,11 @@
<java-symbol type="id" name="clip_children_tag" />
<java-symbol type="drawable" name="ic_reply_notification_large" />
<java-symbol type="dimen" name="messaging_avatar_size" />
+ <java-symbol type="dimen" name="messaging_image_rounding" />
+ <java-symbol type="dimen" name="messaging_image_min_size" />
+ <java-symbol type="dimen" name="messaging_image_max_height" />
+ <java-symbol type="dimen" name="messaging_image_extra_spacing" />
+ <java-symbol type="id" name="messaging_group_icon_container" />
<java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
<java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
@@ -3246,4 +3252,6 @@
<java-symbol type="string" name="screenshot_edit" />
<java-symbol type="bool" name="config_keepRestrictedProfilesInBackground" />
+
+ <java-symbol type="array" name="config_ringtoneEffectUris" />
</resources>
diff --git a/core/res/res/xml/color_extraction.xml b/core/res/res/xml/color_extraction.xml
index 7d52b20..93ab0ff 100644
--- a/core/res/res/xml/color_extraction.xml
+++ b/core/res/res/xml/color_extraction.xml
@@ -257,52 +257,58 @@
<!-- Red Orange -->
<range h="20, 40"
s="0.7, 1"
- l="0.28, 0.643"/>
+ l="0.2, 0.643"/>
<range h="20, 40"
s="0.3, 0.7"
l="0.414, 0.561"/>
<range h="20, 40"
- s="0, 3"
+ s="0, 0.3"
l="0.343, 0.584"/>
<!-- Orange -->
<range h="40, 60"
s="0.7, 1"
- l="0.173, 0.349"/>
+ l="0.173, 0.38"/>
<range h="40, 60"
s="0.3, 0.7"
l="0.233, 0.427"/>
<range h="40, 60"
s="0, 0.3"
- l="0.231, 0.484"/>
+ l="0.231, 0.48"/>
<!-- Yellow 60 -->
<range h="60, 80"
s="0.7, 1"
- l="0.488, 0.737"/>
+ l="0.15, 0.40"/>
<range h="60, 80"
s="0.3, 0.7"
- l="0.673, 0.837"/>
+ l="0.15, 0.42"/>
+ <range h="60, 80"
+ s="0, 0.3"
+ l="0.35, 0.57"/>
<!-- Yellow Green 80 -->
<range h="80, 100"
s="0.7, 1"
- l="0.469, 0.61"/>
+ l="0.36, 0.65"/>
+ <range h="80, 100"
+ s="0.3, 0.7"
+ l="0.48, 0.57"/>
<!-- Yellow green 100 -->
<range h="100, 120"
s="0.7, 1"
- l="0.388, 0.612"/>
+ l="0.388, 0.67"/>
<range h="100, 120"
s="0.3, 0.7"
- l="0.424, 0.541"/>
+ l="0.424, 0.58"/>
<!-- Green -->
<range h="120, 140"
s="0.7, 1"
- l="0.375, 0.52"/>
+ l="0.37, 0.65"/>
<range h="120, 140"
s="0.3, 0.7"
- l="0.435, 0.524"/>
+ l="0.435, 0.58"/>
<!-- Green Blue 140 -->
<range h="140, 160"
s="0.7, 1"
- l="0.496, 0.641"/>
+ l="0.43, 0.641"/>
<!-- Seaoam -->
<range h="160, 180"
s="0.7, 1"
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 7d5c60a..53c22f6 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -51,6 +51,7 @@
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
<uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index dca1656..d5be87d 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -24,6 +24,9 @@
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+ <Space
+ android:layout_width="1dp"
+ android:layout_height="60dp"/>
<TextView
android:id="@+id/nonselectable_textview"
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index aefc47e..fb0f534 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -17,6 +17,7 @@
package android.app.servertransaction;
import static android.app.servertransaction.TestUtils.config;
+import static android.app.servertransaction.TestUtils.mergedConfig;
import static android.app.servertransaction.TestUtils.referrerIntentList;
import static android.app.servertransaction.TestUtils.resultInfoList;
@@ -151,6 +152,25 @@
}
@Test
+ public void testRecycleActivityRelaunchItem() {
+ ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain(null, null, 0, null, false);
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.assetsSeq = 5;
+ ActivityRelaunchItem item = ActivityRelaunchItem.obtain(resultInfoList(),
+ referrerIntentList(), 42, mergedConfig(), true);
+ assertNotSame(item, emptyItem);
+ assertFalse(item.equals(emptyItem));
+
+ item.recycle();
+ assertEquals(item, emptyItem);
+
+ ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(resultInfoList(),
+ referrerIntentList(), 42, mergedConfig(), true);
+ assertSame(item, item2);
+ assertFalse(item2.equals(emptyItem));
+ }
+
+ @Test
public void testRecycleMoveToDisplayItem() {
MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, null);
MoveToDisplayItem item = MoveToDisplayItem.obtain(4, config());
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index e923516..d125fe7 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -21,6 +21,7 @@
import android.app.ResultInfo;
import android.content.Intent;
import android.content.res.Configuration;
+import android.util.MergedConfiguration;
import com.android.internal.content.ReferrerIntent;
@@ -38,6 +39,15 @@
return config;
}
+ static MergedConfiguration mergedConfig() {
+ Configuration config = config();
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.densityDpi = 30;
+ overrideConfig.screenWidthDp = 40;
+ overrideConfig.smallestScreenWidthDp = 15;
+ return new MergedConfiguration(config, overrideConfig);
+ }
+
static List<ResultInfo> resultInfoList() {
String resultWho1 = "resultWho1";
int requestCode1 = 7;
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 77aaa2d..0906435 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -17,6 +17,7 @@
package android.app.servertransaction;
import static android.app.servertransaction.TestUtils.config;
+import static android.app.servertransaction.TestUtils.mergedConfig;
import static android.app.servertransaction.TestUtils.referrerIntentList;
import static android.app.servertransaction.TestUtils.resultInfoList;
@@ -27,7 +28,6 @@
import android.app.IInstrumentationWatcher;
import android.app.IUiAutomationConnection;
import android.app.ProfilerInfo;
-import android.app.ResultInfo;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
@@ -53,7 +53,6 @@
import android.support.test.runner.AndroidJUnit4;
import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.content.ReferrerIntent;
import org.junit.Before;
import org.junit.Test;
@@ -243,6 +242,22 @@
}
@Test
+ public void testRelaunch() {
+ // Write to parcel
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.assetsSeq = 5;
+ ActivityRelaunchItem item = ActivityRelaunchItem.obtain(resultInfoList(),
+ referrerIntentList(), 35, mergedConfig(), true);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ ActivityRelaunchItem result = ActivityRelaunchItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
public void testPause() {
// Write to parcel
PauseActivityItem item = PauseActivityItem.obtain(true /* finished */,
@@ -435,12 +450,6 @@
}
@Override
- public void scheduleRelaunchActivity(IBinder iBinder, List<ResultInfo> list,
- List<ReferrerIntent> list1, int i, boolean b, Configuration configuration,
- Configuration configuration1, boolean b1) throws RemoteException {
- }
-
- @Override
public void scheduleSleeping(IBinder iBinder, boolean b) throws RemoteException {
}
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
new file mode 100644
index 0000000..c7fdf0f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+
+import com.android.internal.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VibrationEffectTest {
+ private static final String RINGTONE_URI_1 = "content://test/system/ringtone_1";
+ private static final String RINGTONE_URI_2 = "content://test/system/ringtone_2";
+ private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
+ private static final String UNKNOWN_URI = "content://test/system/other_audio";
+
+ @Test
+ public void getRingtones_noPrebakedRingtones() {
+ Resources r = mockRingtoneResources(new String[0]);
+ Context context = mockContext(r);
+ VibrationEffect effect = VibrationEffect.get(Uri.parse(RINGTONE_URI_1), context);
+ assertNull(effect);
+ }
+
+ @Test
+ public void getRingtones_noPrebakedRingtoneForUri() {
+ Resources r = mockRingtoneResources();
+ Context context = mockContext(r);
+ VibrationEffect effect = VibrationEffect.get(Uri.parse(UNKNOWN_URI), context);
+ assertNull(effect);
+ }
+
+ @Test
+ public void getRingtones_getPrebakedRingtone() {
+ Resources r = mockRingtoneResources();
+ Context context = mockContext(r);
+ VibrationEffect effect = VibrationEffect.get(Uri.parse(RINGTONE_URI_2), context);
+ VibrationEffect expectedEffect = VibrationEffect.get(VibrationEffect.RINGTONES[1]);
+ assertNotNull(expectedEffect);
+ assertEquals(expectedEffect, effect);
+ }
+
+
+ private Resources mockRingtoneResources() {
+ return mockRingtoneResources(new String[] {
+ RINGTONE_URI_1,
+ RINGTONE_URI_2,
+ RINGTONE_URI_3
+ });
+ }
+
+ private Resources mockRingtoneResources(String[] ringtoneUris) {
+ Resources mockResources = mock(Resources.class);
+ when(mockResources.getStringArray(R.array.config_ringtoneEffectUris))
+ .thenReturn(ringtoneUris);
+ return mockResources;
+ }
+
+ private Context mockContext(Resources r) {
+ Context ctx = mock(Context.class);
+ when(ctx.getResources()).thenReturn(r);
+ return ctx;
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3e62183..853a36d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -109,6 +109,8 @@
Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
Settings.Global.ANIMATOR_DURATION_SCALE,
Settings.Global.ANOMALY_DETECTION_CONSTANTS,
+ Settings.Global.ANOMALY_CONFIG,
+ Settings.Global.ANOMALY_CONFIG_VERSION,
Settings.Global.APN_DB_UPDATE_CONTENT_URL,
Settings.Global.APN_DB_UPDATE_METADATA_URL,
Settings.Global.APP_IDLE_CONSTANTS,
@@ -194,6 +196,7 @@
Settings.Global.DEVICE_PROVISIONED,
Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+ Settings.Global.DISPLAY_PANEL_LPM,
Settings.Global.DISPLAY_SCALING_FORCE,
Settings.Global.DISPLAY_SIZE_FORCED,
Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
@@ -386,7 +389,6 @@
Settings.Global.TZINFO_UPDATE_METADATA_URL,
Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
- Settings.Global.UID_CPUPOWER,
Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
@@ -441,7 +443,7 @@
Settings.Global.WIFI_WATCHDOG_ON,
Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
Settings.Global.WINDOW_ANIMATION_SCALE,
- Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ Settings.Global.CHARGING_STARTED_SOUND,
Settings.Global.WTF_IS_FATAL,
Settings.Global.ZEN_MODE,
Settings.Global.ZEN_MODE_CONFIG_ETAG,
diff --git a/core/tests/coretests/src/android/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS
index 0f85e1f..9f2182e 100644
--- a/core/tests/coretests/src/android/text/OWNERS
+++ b/core/tests/coretests/src/android/text/OWNERS
@@ -1,4 +1,3 @@
siyamed@google.com
nona@google.com
clarabayarri@google.com
-toki@google.com
diff --git a/core/tests/coretests/src/android/view/menu/ContextMenuTest.java b/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
index 59d4e55..657a7fc 100644
--- a/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
+++ b/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
@@ -41,6 +41,13 @@
testMenuPosition(getActivity().getTargetRtl());
}
+ public void testContextMenuPositionRepetitive() throws InterruptedException {
+ // Regression test for b/72507876
+ testMenuPosition(getActivity().getTargetLtr());
+ testMenuPosition(getActivity().getTargetRtl());
+ testMenuPosition(getActivity().getTargetLtr());
+ }
+
private void testMenuPosition(View target) throws InterruptedException {
final int minScreenDimension = getMinScreenDimension();
if (minScreenDimension < 320) {
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 69e5670..7f4f9f7 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -66,6 +66,7 @@
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
import android.test.suitebuilder.annotation.Suppress;
import android.text.InputType;
import android.text.Selection;
@@ -311,15 +312,69 @@
@Test
public void testToolbarAppearsAfterLinkClicked() throws Throwable {
- runToolbarAppearsAfterLinkClickedTest(R.id.textview);
+ TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
+ int position = (textLink.getStart() + textLink.getEnd()) / 2;
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
}
@Test
public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
- runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview);
+ TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+ int position = (textLink.getStart() + textLink.getEnd()) / 2;
+ onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
}
- private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable {
+ @Test
+ public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
+ // Add a link to both selectable and nonselectable TextViews:
+ TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
+ int selectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
+ textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+ int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
+ TextView selectableTextView = mActivity.findViewById(R.id.textview);
+ TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
+
+ onView(withId(R.id.nonselectable_textview))
+ .perform(clickOnTextAtIndex(nonselectablePosition));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+ assertTrue(nonselectableTextView.hasSelection());
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(selectablePosition));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
+ assertTrue(selectableTextView.hasSelection());
+ assertFalse(nonselectableTextView.hasSelection());
+ }
+
+ @Test
+ public void testSelectionRemovedFromNonselectableTextWhenWindowLosesFocus() throws Throwable {
+ TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+ int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
+ TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
+
+ onView(withId(R.id.nonselectable_textview))
+ .perform(clickOnTextAtIndex(nonselectablePosition));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+ assertTrue(nonselectableTextView.hasSelection());
+
+ UiDevice device = UiDevice.getInstance(mInstrumentation);
+ device.openNotification();
+ Thread.sleep(2000);
+ device.pressBack();
+ Thread.sleep(2000);
+
+ assertFalse(nonselectableTextView.hasSelection());
+ }
+
+ private TextLinks.TextLink addLinkifiedTextToTextView(int id) throws Throwable {
TextView textView = mActivity.findViewById(id);
useSystemDefaultTextClassifier();
TextClassificationManager textClassificationManager =
@@ -338,11 +393,7 @@
// Wait for the UI thread to refresh
Thread.sleep(1000);
- TextLinks.TextLink textLink = links.getLinks().iterator().next();
- int position = (textLink.getStart() + textLink.getEnd()) / 2;
- onView(withId(id)).perform(clickOnTextAtIndex(position));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ return links.getLinks().iterator().next();
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 660c744..7b239f0 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -20,6 +20,7 @@
import android.os.Looper;
import android.util.SparseIntArray;
+import com.android.internal.location.gnssmetrics.GnssMetrics;
import java.util.ArrayList;
import java.util.concurrent.Future;
@@ -40,6 +41,11 @@
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
+ for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+ mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null,
+ mOnBatteryTimeBase);
+ }
+
// A no-op handler.
mHandler = new Handler(Looper.getMainLooper()) {};
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index 3919fdd..41082b7 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -127,7 +127,7 @@
assertEquals(355, mView.getMeasuredHeight());;
}
- private class FakeImageFloatingTextView extends MessagingMessage {
+ private class FakeImageFloatingTextView extends MessagingTextMessage {
public static final int LINE_HEIGHT = 50;
private final int mNumLines;
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
index d8e1fc1..b48a46b 100644
--- a/core/tests/overlaytests/host/Android.mk
+++ b/core/tests/overlaytests/host/Android.mk
@@ -24,7 +24,11 @@
OverlayHostTests_BadSignatureOverlay \
OverlayHostTests_PlatformSignatureStaticOverlay \
OverlayHostTests_PlatformSignatureOverlay \
- OverlayHostTests_PlatformSignatureOverlayV2
+ OverlayHostTests_UpdateOverlay \
+ OverlayHostTests_FrameworkOverlayV1 \
+ OverlayHostTests_FrameworkOverlayV2 \
+ OverlayHostTests_AppOverlayV1 \
+ OverlayHostTests_AppOverlayV2
include $(BUILD_HOST_JAVA_LIBRARY)
# Include to build test-apps.
diff --git a/core/tests/overlaytests/host/AndroidTest.xml b/core/tests/overlaytests/host/AndroidTest.xml
index 6884623..1f750a8 100644
--- a/core/tests/overlaytests/host/AndroidTest.xml
+++ b/core/tests/overlaytests/host/AndroidTest.xml
@@ -18,6 +18,11 @@
<option name="test-tag" value="OverlayHostTests" />
<option name="test-suite-tag" value="apct" />
+ <!-- Install the device tests that will be used to verify things on device. -->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="OverlayHostTests_UpdateOverlay.apk" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.HostTest">
<option name="class" value="com.android.server.om.hosttest.InstallOverlayTests" />
</test>
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index 5093710..bf91a16 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -23,14 +23,48 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(DeviceJUnit4ClassRunner.class)
public class InstallOverlayTests extends BaseHostJUnit4Test {
-
- private static final String OVERLAY_PACKAGE_NAME =
+ private static final String SIG_OVERLAY_PACKAGE_NAME =
"com.android.server.om.hosttest.signature_overlay";
+ private static final String APP_OVERLAY_PACKAGE_NAME =
+ "com.android.server.om.hosttest.app_overlay";
+ private static final String FRAMEWORK_OVERLAY_PACKAGE_NAME =
+ "com.android.server.om.hosttest.framework_overlay";
+ private static final String[] ALL_PACKAGES = new String[] {
+ SIG_OVERLAY_PACKAGE_NAME, APP_OVERLAY_PACKAGE_NAME, FRAMEWORK_OVERLAY_PACKAGE_NAME
+ };
+
+ private static final String DEVICE_TEST_PKG =
+ "com.android.server.om.hosttest.update_overlay_test";
+ private static final String DEVICE_TEST_CLS = DEVICE_TEST_PKG + ".UpdateOverlayTest";
+
+ @Before
+ public void ensureNoOverlays() throws Exception {
+ // Make sure we're starting with a clean slate.
+ for (String pkg : ALL_PACKAGES) {
+ assertFalse(pkg + " should not be installed", isPackageInstalled(pkg));
+ assertFalse(pkg + " should not be registered with overlay manager service",
+ overlayManagerContainsPackage(pkg));
+ }
+ }
+
+ /*
+ For some reason, SuiteApkInstaller is *not* uninstalling overlays, even though #installPackage()
+ claims it will auto-clean.
+ TODO(b/72877546): Remove when auto-clean is fixed.
+ */
+ @After
+ public void uninstallOverlays() throws Exception {
+ for (String pkg : ALL_PACKAGES) {
+ uninstallPackage(pkg);
+ }
+ }
@Test
public void failToInstallNonPlatformSignedOverlay() throws Exception {
@@ -40,7 +74,7 @@
} catch (Exception e) {
// Expected.
}
- assertFalse(overlayManagerContainsPackage());
+ assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
}
@Test
@@ -51,28 +85,64 @@
} catch (Exception e) {
// Expected.
}
- assertFalse(overlayManagerContainsPackage());
+ assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
}
@Test
- public void succeedToInstallPlatformSignedOverlay() throws Exception {
+ public void installPlatformSignedOverlay() throws Exception {
installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
- assertTrue(overlayManagerContainsPackage());
+ assertTrue(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
}
@Test
- public void succeedToInstallPlatformSignedOverlayAndUpdate() throws Exception {
- installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
- assertTrue(overlayManagerContainsPackage());
- assertEquals("v1", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+ public void installPlatformSignedAppOverlayAndUpdate() throws Exception {
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource"));
- installPackage("OverlayHostTests_PlatformSignatureOverlayV2.apk");
- assertTrue(overlayManagerContainsPackage());
- assertEquals("v2", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+ installPackage("OverlayHostTests_AppOverlayV1.apk");
+ setOverlayEnabled(APP_OVERLAY_PACKAGE_NAME, true);
+ assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME));
+ assertEquals("v1", getDevice()
+ .getAppPackageInfo(APP_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectAppOverlayV1Resource"));
+
+ installPackage("OverlayHostTests_AppOverlayV2.apk");
+ assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME));
+ assertEquals("v2", getDevice()
+ .getAppPackageInfo(APP_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectAppOverlayV2Resource"));
}
- private boolean overlayManagerContainsPackage() throws Exception {
- return getDevice().executeShellCommand("cmd overlay list")
- .contains(OVERLAY_PACKAGE_NAME);
+ @Test
+ public void installPlatformSignedFrameworkOverlayAndUpdate() throws Exception {
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource"));
+
+ installPackage("OverlayHostTests_FrameworkOverlayV1.apk");
+ setOverlayEnabled(FRAMEWORK_OVERLAY_PACKAGE_NAME, true);
+ assertTrue(overlayManagerContainsPackage(FRAMEWORK_OVERLAY_PACKAGE_NAME));
+ assertEquals("v1", getDevice()
+ .getAppPackageInfo(FRAMEWORK_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectFrameworkOverlayV1Resource"));
+
+ installPackage("OverlayHostTests_FrameworkOverlayV2.apk");
+ assertTrue(overlayManagerContainsPackage(FRAMEWORK_OVERLAY_PACKAGE_NAME));
+ assertEquals("v2", getDevice()
+ .getAppPackageInfo(FRAMEWORK_OVERLAY_PACKAGE_NAME)
+ .getVersionName());
+ assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS,
+ "expectFrameworkOverlayV2Resource"));
+ }
+
+ private void setOverlayEnabled(String pkg, boolean enabled) throws Exception {
+ getDevice().executeShellCommand("cmd overlay " + (enabled ? "enable " : "disable ") + pkg);
+ }
+
+ private boolean overlayManagerContainsPackage(String pkg) throws Exception {
+ return getDevice().executeShellCommand("cmd overlay list").contains(pkg);
}
}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
index b051a82..4249549 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -40,13 +40,4 @@
LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
include $(BUILD_PACKAGE)
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlayV2
-LOCAL_COMPATIBILITY_SUITE := general-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-include $(BUILD_PACKAGE)
-
my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
new file mode 100644
index 0000000..bd6d73d
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
@@ -0,0 +1,73 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+include $(BUILD_PACKAGE)
+
+my_package_prefix := com.android.server.om.hosttest.framework_overlay
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
+LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
+LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
+LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
+LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+my_package_prefix := com.android.server.om.hosttest.app_overlay
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
+LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
+LOCAL_MANIFEST_FILE := app/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
+LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
+LOCAL_MANIFEST_FILE := app/AndroidManifest.xml
+include $(BUILD_PACKAGE)
+
+my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..06077a7
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.update_overlay_test">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.om.hosttest.update_overlay_test"
+ android:label="Update Overlay Test"/>
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml
new file mode 100644
index 0000000..73804eb
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.app_overlay">
+ <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test" />
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml
new file mode 100644
index 0000000..63f85c2
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v1/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_resource">App Resource Overlay V1</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml
new file mode 100644
index 0000000..fa4a697
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_resource">App Resource Overlay V2</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml
new file mode 100644
index 0000000..8c8fe94
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.framework_overlay">
+ <overlay android:targetPackage="android" />
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml
new file mode 100644
index 0000000..fedb2c6
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v1/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="ok">Framework Overlay V1</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml
new file mode 100644
index 0000000..8aebf483
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/framework/v2/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="ok">Framework Overlay V2</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml
new file mode 100644
index 0000000..7393166
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/res/values/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_resource">App Resource</string>
+</resources>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java
new file mode 100644
index 0000000..d46bb37
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.android.server.om.hosttest.update_overlay_test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UpdateOverlayTest {
+ private Resources mResources;
+
+ @Before
+ public void setUp() throws Exception {
+ final Configuration defaultLocaleConfiguration = new Configuration();
+ defaultLocaleConfiguration.setLocale(Locale.US);
+ mResources = InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .createConfigurationContext(defaultLocaleConfiguration)
+ .getResources();
+ }
+
+ @Test
+ public void expectAppResource() throws Exception {
+ assertEquals("App Resource", mResources.getString(R.string.app_resource));
+ }
+
+ @Test
+ public void expectAppOverlayV1Resource() throws Exception {
+ assertEquals("App Resource Overlay V1", mResources.getString(R.string.app_resource));
+ }
+
+ @Test
+ public void expectAppOverlayV2Resource() throws Exception {
+ assertEquals("App Resource Overlay V2", mResources.getString(R.string.app_resource));
+ }
+
+ @Test
+ public void expectFrameworkOverlayResource() throws Exception {
+ assertEquals("OK", mResources.getString(android.R.string.ok));
+ }
+
+ @Test
+ public void expectFrameworkOverlayV1Resource() throws Exception {
+ assertEquals("Framework Overlay V1", mResources.getString(android.R.string.ok));
+ }
+
+ @Test
+ public void expectFrameworkOverlayV2Resource() throws Exception {
+ assertEquals("Framework Overlay V2", mResources.getString(android.R.string.ok));
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index bb9b89b..3495b84 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -158,7 +158,6 @@
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
- <permission name="android.permission.PACKAGE_USAGE_STATS"/>
<permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 1ef74ba..d0565ca7 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -472,11 +472,15 @@
### Non-printing keys ###
key ESCAPE {
- base: fallback BACK
+ base: none
alt, meta: fallback HOME
ctrl: fallback MENU
}
+key DEL {
+ ctrl+alt: fallback BACK
+}
+
### Gamepad buttons ###
key BUTTON_A {
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index c4647e0..c763cc09 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -469,11 +469,15 @@
### Non-printing keys ###
key ESCAPE {
- base: fallback BACK
+ base: none
alt, meta: fallback HOME
ctrl: fallback MENU
}
+key DEL {
+ ctrl+alt: fallback BACK
+}
+
### Gamepad buttons ###
key BUTTON_A {
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index f5bf754..7ea35e7 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -18,6 +18,8 @@
import static android.graphics.BitmapFactory.Options.validate;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Trace;
@@ -518,8 +520,9 @@
* is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
* function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
*/
- public static Bitmap decodeResourceStream(Resources res, TypedValue value,
- InputStream is, Rect pad, Options opts) {
+ @Nullable
+ public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
+ @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
@@ -707,7 +710,9 @@
* <code>is.mark(1024)</code> would be called. As of
* {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p>
*/
- public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+ @Nullable
+ public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
+ @Nullable Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
@@ -742,7 +747,8 @@
* Private helper function for decoding an InputStream natively. Buffers the input enough to
* do a rewind as needed, and supplies temporary storage if necessary. is MUST NOT be null.
*/
- private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
+ private static Bitmap decodeStreamInternal(@NonNull InputStream is,
+ @Nullable Rect outPadding, @Nullable Options opts) {
// ASSERT(is != null);
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index f5e8633..d925441 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1219,10 +1219,14 @@
nFreeTextLayoutCaches();
}
+ /** @hide */
+ public static void setCompatibilityVersion(int apiLevel) { nSetCompatibilityVersion(apiLevel); }
+
private static native void nFreeCaches();
private static native void nFreeTextLayoutCaches();
private static native long nInitRaster(Bitmap bitmap);
private static native long nGetNativeFinalizer();
+ private static native void nSetCompatibilityVersion(int apiLevel);
// ---------------- @FastNative -------------------
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 04c5295..f41267e 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -60,6 +60,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
@@ -801,36 +802,18 @@
* @return The new typeface.
*/
public static Typeface createFromAsset(AssetManager mgr, String path) {
- if (path == null) {
- throw new NullPointerException(); // for backward compatibility
- }
- synchronized (sDynamicCacheLock) {
- Typeface typeface = new Builder(mgr, path).build();
- if (typeface != null) return typeface;
+ Preconditions.checkNotNull(path); // for backward compatibility
+ Preconditions.checkNotNull(mgr);
- final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
- null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
- DEFAULT_FAMILY);
- typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
-
- final FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
- 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
- null /* axes */)) {
- if (!fontFamily.freeze()) {
- return Typeface.DEFAULT;
- }
- final FontFamily[] families = { fontFamily };
- typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- sDynamicTypefaceCache.put(key, typeface);
- return typeface;
- } else {
- fontFamily.abortCreation();
- }
+ Typeface typeface = new Builder(mgr, path).build();
+ if (typeface != null) return typeface;
+ // check if the file exists, and throw an exception for backward compatibility
+ try (InputStream inputStream = mgr.open(path)) {
+ } catch (IOException e) {
+ throw new RuntimeException("Font asset not found " + path);
}
- throw new RuntimeException("Font asset not found " + path);
+
+ return Typeface.DEFAULT;
}
/**
@@ -848,13 +831,22 @@
/**
* Create a new typeface from the specified font file.
*
- * @param path The path to the font data.
+ * @param file The path to the font data.
* @return The new typeface.
*/
- public static Typeface createFromFile(@Nullable File path) {
+ public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
- return createFromFile(path.getAbsolutePath());
+
+ Typeface typeface = new Builder(file).build();
+ if (typeface != null) return typeface;
+
+ // check if the file exists, and throw an exception for backward compatibility
+ if (!file.exists()) {
+ throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
+ }
+
+ return Typeface.DEFAULT;
}
/**
@@ -864,19 +856,8 @@
* @return The new typeface.
*/
public static Typeface createFromFile(@Nullable String path) {
- final FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) {
- if (!fontFamily.freeze()) {
- return Typeface.DEFAULT;
- }
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- } else {
- fontFamily.abortCreation();
- }
- throw new RuntimeException("Font not found " + path);
+ Preconditions.checkNotNull(path); // for backward compatibility
+ return createFromFile(new File(path));
}
/**
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 36a4d26..8af2fd8 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1171,9 +1171,13 @@
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
+ *
+ * @deprecated Prefer the version without an Options object.
*/
- public static Drawable createFromResourceStream(Resources res, TypedValue value,
- InputStream is, String srcName, BitmapFactory.Options opts) {
+ @Nullable
+ public static Drawable createFromResourceStream(@Nullable Resources res,
+ @Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
+ @Nullable BitmapFactory.Options opts) {
if (is == null) {
return null;
}
@@ -1197,7 +1201,6 @@
// an application in compatibility mode, without scaling those down
// to the compatibility density only to have them scaled back up when
// drawn to the screen.
- if (opts == null) opts = new BitmapFactory.Options();
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
if (bm != null) {
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index f5a6f49..8b5114c 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -825,6 +825,14 @@
mFillPaint.setXfermode(mode);
}
+ /**
+ * @param aa to draw this drawable with
+ * @hide
+ */
+ public void setAntiAlias(boolean aa) {
+ mFillPaint.setAntiAlias(aa);
+ }
+
private void buildPathIfDirty() {
final GradientState st = mGradientState;
if (mPathIsDirty) {
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index 4129868..a8dc34a 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -110,6 +110,7 @@
// Take 60% of the maximum of the width and height, then divided half to get the radius.
mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f;
+ clampStartingPosition();
}
@Override
@@ -350,7 +351,7 @@
final float cY = mBounds.exactCenterY();
final float dX = mStartingX - cX;
final float dY = mStartingY - cY;
- final float r = mTargetRadius;
+ final float r = mTargetRadius - mStartRadius;
if (dX * dX + dY * dY > r * r) {
// Point is outside the circle, clamp to the perimeter.
final double angle = Math.atan2(dY, dX);
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 251b2e7..70d5216 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -145,6 +145,7 @@
"tests/TypeWrappers_test.cpp",
"tests/ZipUtils_test.cpp",
],
+ static_libs: ["libgmock"],
target: {
android: {
srcs: [
@@ -171,6 +172,7 @@
// Actual benchmarks.
"tests/AssetManager2_bench.cpp",
+ "tests/AttributeResolution_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index da0205d..60f8a18 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -231,12 +231,16 @@
while ((result = ::Next(cookie, &entry, &name)) == 0) {
StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length);
StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
- auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
- if (iter != leaf_file_path.end()) {
- dirs.insert(
- leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string());
- } else if (!leaf_file_path.empty()) {
- f(leaf_file_path, kFileTypeRegular);
+
+ if (!leaf_file_path.empty()) {
+ auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
+ if (iter != leaf_file_path.end()) {
+ std::string dir =
+ leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+ dirs.insert(std::move(dir));
+ } else {
+ f(leaf_file_path, kFileTypeRegular);
+ }
}
}
::EndIteration(cookie);
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 415d3e3..a8c916b 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -36,6 +36,31 @@
namespace android {
+struct FindEntryResult {
+ // A pointer to the resource table entry for this resource.
+ // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
+ // a ResTable_map_entry and processed as a bag/map.
+ const ResTable_entry* entry;
+
+ // The configuration for which the resulting entry was defined. This is already swapped to host
+ // endianness.
+ ResTable_config config;
+
+ // The bitmask of configuration axis with which the resource value varies.
+ uint32_t type_flags;
+
+ // The dynamic package ID map for the package from which this resource came from.
+ const DynamicRefTable* dynamic_ref_table;
+
+ // The string pool reference to the type's name. This uses a different string pool than
+ // the global string pool, but this is hidden from the caller.
+ StringPoolRef type_string_ref;
+
+ // The string pool reference to the entry's name. This uses a different string pool than
+ // the global string pool, but this is hidden from the caller.
+ StringPoolRef entry_string_ref;
+};
+
AssetManager2::AssetManager2() {
memset(&configuration_, 0, sizeof(configuration_));
}
@@ -44,6 +69,7 @@
bool invalidate_caches) {
apk_assets_ = apk_assets;
BuildDynamicRefTable();
+ RebuildFilterList();
if (invalidate_caches) {
InvalidateCaches(static_cast<uint32_t>(-1));
}
@@ -74,12 +100,14 @@
if (idx == 0xff) {
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
package_groups_.push_back({});
- package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id;
+ DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;
+ ref_table.mAssignedPackageId = package_id;
+ ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
- package_group->packages_.push_back(package.get());
+ package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// Add the package name -> build time ID mappings.
@@ -94,7 +122,7 @@
// Now assign the runtime IDs so that we have a build-time to runtime ID map.
const auto package_groups_end = package_groups_.end();
for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
- const std::string& package_name = iter->packages_[0]->GetPackageName();
+ const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
iter->dynamic_ref_table.mAssignedPackageId);
@@ -105,20 +133,33 @@
void AssetManager2::DumpToLog() const {
base::ScopedLogSeverity _log(base::INFO);
+ LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this);
+
std::string list;
+ for (const auto& apk_assets : apk_assets_) {
+ base::StringAppendF(&list, "%s,", apk_assets->GetPath().c_str());
+ }
+ LOG(INFO) << "ApkAssets: " << list;
+
+ list = "";
for (size_t i = 0; i < package_ids_.size(); i++) {
if (package_ids_[i] != 0xff) {
- base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]);
+ base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]);
}
}
LOG(INFO) << "Package ID map: " << list;
for (const auto& package_group: package_groups_) {
- list = "";
- for (const auto& package : package_group.packages_) {
- base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId());
- }
- LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list;
+ list = "";
+ for (const auto& package : package_group.packages_) {
+ const LoadedPackage* loaded_package = package.loaded_package_;
+ base::StringAppendF(&list, "%s(%02x%s), ", loaded_package->GetPackageName().c_str(),
+ loaded_package->GetPackageId(),
+ (loaded_package->IsDynamic() ? " dynamic" : ""));
+ }
+ LOG(INFO) << base::StringPrintf("PG (%02x): ",
+ package_group.dynamic_ref_table.mAssignedPackageId)
+ << list;
}
}
@@ -157,52 +198,54 @@
configuration_ = configuration;
if (diff) {
+ RebuildFilterList();
InvalidateCaches(static_cast<uint32_t>(diff));
}
}
std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system,
- bool exclude_mipmap) {
+ bool exclude_mipmap) const {
ATRACE_CALL();
std::set<ResTable_config> configurations;
for (const PackageGroup& package_group : package_groups_) {
- for (const LoadedPackage* package : package_group.packages_) {
- if (exclude_system && package->IsSystem()) {
+ for (const ConfiguredPackage& package : package_group.packages_) {
+ if (exclude_system && package.loaded_package_->IsSystem()) {
continue;
}
- package->CollectConfigurations(exclude_mipmap, &configurations);
+ package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations);
}
}
return configurations;
}
std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
- bool merge_equivalent_languages) {
+ bool merge_equivalent_languages) const {
ATRACE_CALL();
std::set<std::string> locales;
for (const PackageGroup& package_group : package_groups_) {
- for (const LoadedPackage* package : package_group.packages_) {
- if (exclude_system && package->IsSystem()) {
+ for (const ConfiguredPackage& package : package_group.packages_) {
+ if (exclude_system && package.loaded_package_->IsSystem()) {
continue;
}
- package->CollectLocales(merge_equivalent_languages, &locales);
+ package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales);
}
}
return locales;
}
-std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) {
+std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename,
+ Asset::AccessMode mode) const {
const std::string new_path = "assets/" + filename;
return OpenNonAsset(new_path, mode);
}
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
- Asset::AccessMode mode) {
+ Asset::AccessMode mode) const {
const std::string new_path = "assets/" + filename;
return OpenNonAsset(new_path, cookie, mode);
}
-std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
+std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const {
ATRACE_CALL();
std::string full_path = "assets/" + dirname;
@@ -236,7 +279,7 @@
// is inconsistent for split APKs.
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
Asset::AccessMode mode,
- ApkAssetsCookie* out_cookie) {
+ ApkAssetsCookie* out_cookie) const {
ATRACE_CALL();
for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
@@ -255,7 +298,8 @@
}
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
- ApkAssetsCookie cookie, Asset::AccessMode mode) {
+ ApkAssetsCookie cookie,
+ Asset::AccessMode mode) const {
ATRACE_CALL();
if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
return {};
@@ -264,14 +308,13 @@
}
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
- bool stop_at_first_match, FindEntryResult* out_entry) {
- ATRACE_CALL();
-
+ bool /*stop_at_first_match*/,
+ FindEntryResult* out_entry) const {
// Might use this if density_override != 0.
ResTable_config density_override_config;
// Select our configuration or generate a density override configuration.
- ResTable_config* desired_config = &configuration_;
+ const ResTable_config* desired_config = &configuration_;
if (density_override != 0 && density_override != configuration_.density) {
density_override_config = configuration_;
density_override_config.density = density_override;
@@ -285,53 +328,135 @@
const uint32_t package_id = get_package_id(resid);
const uint8_t type_idx = get_type_id(resid) - 1;
- const uint16_t entry_id = get_entry_id(resid);
+ const uint16_t entry_idx = get_entry_id(resid);
- const uint8_t idx = package_ids_[package_id];
- if (idx == 0xff) {
+ const uint8_t package_idx = package_ids_[package_id];
+ if (package_idx == 0xff) {
LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
return kInvalidCookie;
}
- FindEntryResult best_entry;
- ApkAssetsCookie best_cookie = kInvalidCookie;
- uint32_t cumulated_flags = 0u;
-
- const PackageGroup& package_group = package_groups_[idx];
+ const PackageGroup& package_group = package_groups_[package_idx];
const size_t package_count = package_group.packages_.size();
- FindEntryResult current_entry;
- for (size_t i = 0; i < package_count; i++) {
- const LoadedPackage* loaded_package = package_group.packages_[i];
- if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) {
+
+ ApkAssetsCookie best_cookie = kInvalidCookie;
+ const LoadedPackage* best_package = nullptr;
+ const ResTable_type* best_type = nullptr;
+ const ResTable_config* best_config = nullptr;
+ ResTable_config best_config_copy;
+ uint32_t best_offset = 0u;
+ uint32_t type_flags = 0u;
+
+ // If desired_config is the same as the set configuration, then we can use our filtered list
+ // and we don't need to match the configurations, since they already matched.
+ const bool use_fast_path = desired_config == &configuration_;
+
+ for (size_t pi = 0; pi < package_count; pi++) {
+ const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
+ const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
+ ApkAssetsCookie cookie = package_group.cookies_[pi];
+
+ // If the type IDs are offset in this package, we need to take that into account when searching
+ // for a type.
+ const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
+ if (UNLIKELY(type_spec == nullptr)) {
continue;
}
- cumulated_flags |= current_entry.type_flags;
+ uint16_t local_entry_idx = entry_idx;
- const ResTable_config* current_config = current_entry.config;
- const ResTable_config* best_config = best_entry.config;
- if (best_cookie == kInvalidCookie ||
- current_config->isBetterThan(*best_config, desired_config) ||
- (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) {
- best_entry = current_entry;
- best_cookie = package_group.cookies_[i];
- if (stop_at_first_match) {
- break;
+ // If there is an IDMAP supplied with this package, translate the entry ID.
+ if (type_spec->idmap_entries != nullptr) {
+ if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) {
+ // There is no mapping, so the resource is not meant to be in this overlay package.
+ continue;
+ }
+ }
+
+ type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
+
+ // If the package is an overlay, then even configurations that are the same MUST be chosen.
+ const bool package_is_overlay = loaded_package->IsOverlay();
+
+ const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
+ if (use_fast_path) {
+ const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
+ const size_t type_count = candidate_configs.size();
+ for (uint32_t i = 0; i < type_count; i++) {
+ const ResTable_config& this_config = candidate_configs[i];
+
+ // We can skip calling ResTable_config::match() because we know that all candidate
+ // configurations that do NOT match have been filtered-out.
+ if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
+ (package_is_overlay && this_config.compare(*best_config) == 0)) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const ResTable_type* type_chunk = filtered_group.types[i];
+ const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ best_cookie = cookie;
+ best_package = loaded_package;
+ best_type = type_chunk;
+ best_config = &this_config;
+ best_offset = offset;
+ }
+ }
+ } else {
+ // This is the slower path, which doesn't use the filtered list of configurations.
+ // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness
+ // and fill in any new fields that did not exist when the APK was compiled.
+ // Furthermore when selecting configurations we can't just record the pointer to the
+ // ResTable_config, we must copy it.
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ ResTable_config this_config;
+ this_config.copyFromDtoH((*iter)->config);
+
+ if (this_config.match(*desired_config)) {
+ if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
+ (package_is_overlay && this_config.compare(*best_config) == 0)) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ best_cookie = cookie;
+ best_package = loaded_package;
+ best_type = *iter;
+ best_config_copy = this_config;
+ best_config = &best_config_copy;
+ best_offset = offset;
+ }
+ }
}
}
}
- if (best_cookie == kInvalidCookie) {
+ if (UNLIKELY(best_cookie == kInvalidCookie)) {
return kInvalidCookie;
}
- *out_entry = best_entry;
+ const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (UNLIKELY(best_entry == nullptr)) {
+ return kInvalidCookie;
+ }
+
+ out_entry->entry = best_entry;
+ out_entry->config = *best_config;
+ out_entry->type_flags = type_flags;
+ out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
+ out_entry->entry_string_ref =
+ StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
- out_entry->type_flags = cumulated_flags;
return best_cookie;
}
-bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
+bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
ATRACE_CALL();
FindEntryResult entry;
@@ -341,7 +466,8 @@
return false;
}
- const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid);
+ const LoadedPackage* package =
+ apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
if (package == nullptr) {
return false;
}
@@ -369,7 +495,7 @@
return true;
}
-bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
+bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const {
FindEntryResult entry;
ApkAssetsCookie cookie =
FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
@@ -383,7 +509,7 @@
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
uint16_t density_override, Res_value* out_value,
ResTable_config* out_selected_config,
- uint32_t* out_flags) {
+ uint32_t* out_flags) const {
ATRACE_CALL();
FindEntryResult entry;
@@ -402,7 +528,7 @@
// Create a reference since we can't represent this complex type as a Res_value.
out_value->dataType = Res_value::TYPE_REFERENCE;
out_value->data = resid;
- *out_selected_config = *entry.config;
+ *out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
@@ -414,7 +540,7 @@
// Convert the package ID to the runtime assigned package ID.
entry.dynamic_ref_table->lookupResourceValue(out_value);
- *out_selected_config = *entry.config;
+ *out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
@@ -422,16 +548,14 @@
ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config,
uint32_t* in_out_flags,
- uint32_t* out_last_reference) {
+ uint32_t* out_last_reference) const {
ATRACE_CALL();
constexpr const int kMaxIterations = 20;
for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
in_out_value->data != 0u && iteration < kMaxIterations;
iteration++) {
- if (out_last_reference != nullptr) {
- *out_last_reference = in_out_value->data;
- }
+ *out_last_reference = in_out_value->data;
uint32_t new_flags = 0u;
cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/,
in_out_value, in_out_selected_config, &new_flags);
@@ -492,7 +616,8 @@
// Attributes, arrays, etc don't have a resource id as the name. They specify
// other data, which would be wrong to change via a lookup.
if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
+ resid);
return nullptr;
}
}
@@ -524,7 +649,8 @@
const ResolvedBag* parent_bag = GetBag(parent_resid);
if (parent_bag == nullptr) {
// Failed to get the parent that should exist.
- LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid,
+ resid);
return nullptr;
}
@@ -543,7 +669,8 @@
uint32_t child_key = dtohl(map_entry->name.ident);
if (!is_internal_resid(child_key)) {
if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key,
+ resid);
return nullptr;
}
}
@@ -582,7 +709,8 @@
uint32_t new_key = dtohl(map_entry->name.ident);
if (!is_internal_resid(new_key)) {
if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
+ resid);
return nullptr;
}
}
@@ -638,7 +766,7 @@
uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
const std::string& fallback_type,
- const std::string& fallback_package) {
+ const std::string& fallback_package) const {
StringPiece package_name, type, entry;
if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) {
return 0u;
@@ -670,7 +798,8 @@
const static std::u16string kAttrPrivate16 = u"^attr-private";
for (const PackageGroup& package_group : package_groups_) {
- for (const LoadedPackage* package : package_group.packages_) {
+ for (const ConfiguredPackage& package_impl : package_group.packages_) {
+ const LoadedPackage* package = package_impl.loaded_package_;
if (package_name != package->GetPackageName()) {
// All packages in the same group are expected to have the same package name.
break;
@@ -692,6 +821,32 @@
return 0u;
}
+void AssetManager2::RebuildFilterList() {
+ for (PackageGroup& group : package_groups_) {
+ for (ConfiguredPackage& impl : group.packages_) {
+ // Destroy it.
+ impl.filtered_configs_.~ByteBucketArray();
+
+ // Re-create it.
+ new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
+
+ // Create the filters here.
+ impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
+ FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
+ const auto iter_end = spec->types + spec->type_count;
+ for (auto iter = spec->types; iter != iter_end; ++iter) {
+ ResTable_config this_config;
+ this_config.copyFromDtoH((*iter)->config);
+ if (this_config.match(configuration_)) {
+ group.configurations.push_back(this_config);
+ group.types.push_back(*iter);
+ }
+ }
+ });
+ }
+ }
+}
+
void AssetManager2::InvalidateCaches(uint32_t diff) {
if (diff == 0xffffffffu) {
// Everything must go.
@@ -872,7 +1027,7 @@
ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config,
uint32_t* in_out_type_spec_flags,
- uint32_t* out_last_ref) {
+ uint32_t* out_last_ref) const {
if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) {
uint32_t new_flags;
cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags);
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index 60e3845..f912af4 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -20,13 +20,18 @@
#include <log/log.h>
+#include "androidfw/AssetManager2.h"
#include "androidfw/AttributeFinder.h"
-#include "androidfw/ResourceTypes.h"
constexpr bool kDebugStyles = false;
namespace android {
+// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
+static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
+ return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1);
+}
+
class XmlAttributeFinder
: public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> {
public:
@@ -44,58 +49,53 @@
};
class BagAttributeFinder
- : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> {
+ : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> {
public:
- BagAttributeFinder(const ResTable::bag_entry* start,
- const ResTable::bag_entry* end)
- : BackTrackingAttributeFinder(start, end) {}
+ BagAttributeFinder(const ResolvedBag* bag)
+ : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr,
+ bag != nullptr ? bag->entries + bag->entry_count : nullptr) {
+ }
- inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const {
- return entry->map.name.ident;
+ inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const {
+ return entry->key;
}
};
-bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr,
- uint32_t def_style_res, uint32_t* src_values,
- size_t src_values_length, uint32_t* attrs,
- size_t attrs_length, uint32_t* out_values,
- uint32_t* out_indices) {
+bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+ uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
if (kDebugStyles) {
ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme,
def_style_attr, def_style_res);
}
- const ResTable& res = theme->getResTable();
+ AssetManager2* assetmanager = theme->GetAssetManager();
ResTable_config config;
Res_value value;
int indices_idx = 0;
// Load default style from attribute, if specified...
- uint32_t def_style_bag_type_set_flags = 0;
+ uint32_t def_style_flags = 0u;
if (def_style_attr != 0) {
Res_value value;
- if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) {
+ if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
def_style_res = value.data;
}
}
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
// Retrieve the default style bag, if requested.
- const ResTable::bag_entry* def_style_start = nullptr;
- uint32_t def_style_type_set_flags = 0;
- ssize_t bag_off = def_style_res != 0
- ? res.getBagLocked(def_style_res, &def_style_start,
- &def_style_type_set_flags)
- : -1;
- def_style_type_set_flags |= def_style_bag_type_set_flags;
- const ResTable::bag_entry* const def_style_end =
- def_style_start + (bag_off >= 0 ? bag_off : 0);
- BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end);
+ const ResolvedBag* default_style_bag = nullptr;
+ if (def_style_res != 0) {
+ default_style_bag = assetmanager->GetBag(def_style_res);
+ if (default_style_bag != nullptr) {
+ def_style_flags |= default_style_bag->type_spec_flags;
+ }
+ }
+
+ BagAttributeFinder def_style_attr_finder(default_style_bag);
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
@@ -106,7 +106,7 @@
ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
}
- ssize_t block = -1;
+ ApkAssetsCookie cookie = kInvalidCookie;
uint32_t type_set_flags = 0;
value.dataType = Res_value::TYPE_NULL;
@@ -122,15 +122,14 @@
value.dataType = Res_value::TYPE_ATTRIBUTE;
value.data = src_values[ii];
if (kDebugStyles) {
- ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
}
} else {
- const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident);
- if (def_style_entry != def_style_end) {
- block = def_style_entry->stringBlock;
- type_set_flags = def_style_type_set_flags;
- value = def_style_entry->map.value;
+ const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident);
+ if (entry != def_style_attr_finder.end()) {
+ cookie = entry->cookie;
+ type_set_flags = def_style_flags;
+ value = entry->value;
if (kDebugStyles) {
ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
@@ -140,22 +139,26 @@
uint32_t resid = 0;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- ssize_t new_block =
- theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
- if (new_block >= 0) block = new_block;
+ ApkAssetsCookie new_cookie =
+ theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
+ }
if (kDebugStyles) {
ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
}
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
- // If we still don't have a value for this attribute, try to find
- // it in the theme!
- ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
- if (new_block >= 0) {
+ // If we still don't have a value for this attribute, try to find it in the theme!
+ ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
+ if (new_cookie != kInvalidCookie) {
if (kDebugStyles) {
ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
- if (new_block >= 0) block = new_block;
+ new_cookie =
+ assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
+ }
if (kDebugStyles) {
ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
@@ -169,7 +172,7 @@
}
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
- block = -1;
+ cookie = kInvalidCookie;
}
if (kDebugStyles) {
@@ -179,9 +182,7 @@
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
- out_values[STYLE_ASSET_COOKIE] =
- block != -1 ? static_cast<uint32_t>(res.getTableCookie(block))
- : static_cast<uint32_t>(-1);
+ out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
@@ -195,90 +196,80 @@
out_values += STYLE_NUM_ENTRIES;
}
- res.unlock();
-
if (out_indices != nullptr) {
out_indices[0] = indices_idx;
}
return true;
}
-void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
- uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
uint32_t* out_values, uint32_t* out_indices) {
if (kDebugStyles) {
- ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p",
- theme, def_style_attr, def_style_res, xml_parser);
+ ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
+ def_style_attr, def_style_resid, xml_parser);
}
- const ResTable& res = theme->getResTable();
+ AssetManager2* assetmanager = theme->GetAssetManager();
ResTable_config config;
Res_value value;
int indices_idx = 0;
// Load default style from attribute, if specified...
- uint32_t def_style_bag_type_set_flags = 0;
+ uint32_t def_style_flags = 0u;
if (def_style_attr != 0) {
Res_value value;
- if (theme->getAttribute(def_style_attr, &value,
- &def_style_bag_type_set_flags) >= 0) {
+ if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
- def_style_res = value.data;
+ def_style_resid = value.data;
}
}
}
- // Retrieve the style class associated with the current XML tag.
- int style = 0;
- uint32_t style_bag_type_set_flags = 0;
+ // Retrieve the style resource ID associated with the current XML tag's style attribute.
+ uint32_t style_resid = 0u;
+ uint32_t style_flags = 0u;
if (xml_parser != nullptr) {
ssize_t idx = xml_parser->indexOfStyle();
if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
if (value.dataType == value.TYPE_ATTRIBUTE) {
- if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) {
+ // Resolve the attribute with out theme.
+ if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) {
value.dataType = Res_value::TYPE_NULL;
}
}
+
if (value.dataType == value.TYPE_REFERENCE) {
- style = value.data;
+ style_resid = value.data;
}
}
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
// Retrieve the default style bag, if requested.
- const ResTable::bag_entry* def_style_attr_start = nullptr;
- uint32_t def_style_type_set_flags = 0;
- ssize_t bag_off = def_style_res != 0
- ? res.getBagLocked(def_style_res, &def_style_attr_start,
- &def_style_type_set_flags)
- : -1;
- def_style_type_set_flags |= def_style_bag_type_set_flags;
- const ResTable::bag_entry* const def_style_attr_end =
- def_style_attr_start + (bag_off >= 0 ? bag_off : 0);
- BagAttributeFinder def_style_attr_finder(def_style_attr_start,
- def_style_attr_end);
+ const ResolvedBag* default_style_bag = nullptr;
+ if (def_style_resid != 0) {
+ default_style_bag = assetmanager->GetBag(def_style_resid);
+ if (default_style_bag != nullptr) {
+ def_style_flags |= default_style_bag->type_spec_flags;
+ }
+ }
+
+ BagAttributeFinder def_style_attr_finder(default_style_bag);
// Retrieve the style class bag, if requested.
- const ResTable::bag_entry* style_attr_start = nullptr;
- uint32_t style_type_set_flags = 0;
- bag_off =
- style != 0
- ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags)
- : -1;
- style_type_set_flags |= style_bag_type_set_flags;
- const ResTable::bag_entry* const style_attr_end =
- style_attr_start + (bag_off >= 0 ? bag_off : 0);
- BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end);
+ const ResolvedBag* xml_style_bag = nullptr;
+ if (style_resid != 0) {
+ xml_style_bag = assetmanager->GetBag(style_resid);
+ if (xml_style_bag != nullptr) {
+ style_flags |= xml_style_bag->type_spec_flags;
+ }
+ }
+
+ BagAttributeFinder xml_style_attr_finder(xml_style_bag);
// Retrieve the XML attributes, if requested.
- static const ssize_t kXmlBlock = 0x10000000;
XmlAttributeFinder xml_attr_finder(xml_parser);
- const size_t xml_attr_end =
- xml_parser != nullptr ? xml_parser->getAttributeCount() : 0;
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
@@ -289,8 +280,8 @@
ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
}
- ssize_t block = kXmlBlock;
- uint32_t type_set_flags = 0;
+ ApkAssetsCookie cookie = kInvalidCookie;
+ uint32_t type_set_flags = 0u;
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
@@ -302,7 +293,7 @@
// Walk through the xml attributes looking for the requested attribute.
const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
- if (xml_attr_idx != xml_attr_end) {
+ if (xml_attr_idx != xml_attr_finder.end()) {
// We found the attribute we were looking for.
xml_parser->getAttributeValue(xml_attr_idx, &value);
if (kDebugStyles) {
@@ -312,12 +303,12 @@
if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
// Walk through the style class values looking for the requested attribute.
- const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident);
- if (style_attr_entry != style_attr_end) {
+ const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
+ if (entry != xml_style_attr_finder.end()) {
// We found the attribute we were looking for.
- block = style_attr_entry->stringBlock;
- type_set_flags = style_type_set_flags;
- value = style_attr_entry->map.value;
+ cookie = entry->cookie;
+ type_set_flags = style_flags;
+ value = entry->value;
if (kDebugStyles) {
ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
@@ -326,25 +317,25 @@
if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
// Walk through the default style values looking for the requested attribute.
- const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident);
- if (def_style_attr_entry != def_style_attr_end) {
+ const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
+ if (entry != def_style_attr_finder.end()) {
// We found the attribute we were looking for.
- block = def_style_attr_entry->stringBlock;
- type_set_flags = style_type_set_flags;
- value = def_style_attr_entry->map.value;
+ cookie = entry->cookie;
+ type_set_flags = def_style_flags;
+ value = entry->value;
if (kDebugStyles) {
ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
- uint32_t resid = 0;
+ uint32_t resid = 0u;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- ssize_t new_block =
- theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
- if (new_block >= 0) {
- block = new_block;
+ ApkAssetsCookie new_cookie =
+ theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
}
if (kDebugStyles) {
@@ -352,14 +343,15 @@
}
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
// If we still don't have a value for this attribute, try to find it in the theme!
- ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
- if (new_block >= 0) {
+ ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
+ if (new_cookie != kInvalidCookie) {
if (kDebugStyles) {
ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
- if (new_block >= 0) {
- block = new_block;
+ new_cookie =
+ assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
}
if (kDebugStyles) {
@@ -375,7 +367,7 @@
}
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
- block = kXmlBlock;
+ cookie = kInvalidCookie;
}
if (kDebugStyles) {
@@ -385,9 +377,7 @@
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
- out_values[STYLE_ASSET_COOKIE] =
- block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block))
- : static_cast<uint32_t>(-1);
+ out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
@@ -402,36 +392,28 @@
out_values += STYLE_NUM_ENTRIES;
}
- res.unlock();
-
// out_indices must NOT be nullptr.
out_indices[0] = indices_idx;
}
-bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser,
- uint32_t* attrs, size_t attrs_length,
- uint32_t* out_values, uint32_t* out_indices) {
+bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
ResTable_config config;
Res_value value;
int indices_idx = 0;
- // Now lock down the resource object and start pulling stuff from it.
- res->lock();
-
// Retrieve the XML attributes, if requested.
const size_t xml_attr_count = xml_parser->getAttributeCount();
size_t ix = 0;
uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix);
- static const ssize_t kXmlBlock = 0x10000000;
-
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
for (size_t ii = 0; ii < attrs_length; ii++) {
const uint32_t cur_ident = attrs[ii];
- ssize_t block = kXmlBlock;
- uint32_t type_set_flags = 0;
+ ApkAssetsCookie cookie = kInvalidCookie;
+ uint32_t type_set_flags = 0u;
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
@@ -450,28 +432,27 @@
cur_xml_attr = xml_parser->getAttributeNameResID(ix);
}
- uint32_t resid = 0;
+ uint32_t resid = 0u;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- // printf("Resolving attribute reference\n");
- ssize_t new_block = res->resolveReference(&value, block, &resid,
- &type_set_flags, &config);
- if (new_block >= 0) block = new_block;
+ ApkAssetsCookie new_cookie =
+ assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
+ }
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
- block = kXmlBlock;
+ cookie = kInvalidCookie;
}
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
- out_values[STYLE_ASSET_COOKIE] =
- block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block))
- : static_cast<uint32_t>(-1);
+ out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
@@ -485,8 +466,6 @@
out_values += STYLE_NUM_ENTRIES;
}
- res->unlock();
-
if (out_indices != nullptr) {
out_indices[0] = indices_idx;
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 28548e2..a65d49b 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -44,44 +44,6 @@
constexpr const static int kAppPackageId = 0x7f;
-// Element of a TypeSpec array. See TypeSpec.
-struct Type {
- // The configuration for which this type defines entries.
- // This is already converted to host endianness.
- ResTable_config configuration;
-
- // Pointer to the mmapped data where entry definitions are kept.
- const ResTable_type* type;
-};
-
-// TypeSpec is going to be immediately proceeded by
-// an array of Type structs, all in the same block of memory.
-struct TypeSpec {
- // Pointer to the mmapped data where flags are kept.
- // Flags denote whether the resource entry is public
- // and under which configurations it varies.
- const ResTable_typeSpec* type_spec;
-
- // Pointer to the mmapped data where the IDMAP mappings for this type
- // exist. May be nullptr if no IDMAP exists.
- const IdmapEntry_header* idmap_entries;
-
- // The number of types that follow this struct.
- // There is a type for each configuration
- // that entries are defined for.
- size_t type_count;
-
- // Trick to easily access a variable number of Type structs
- // proceeding this struct, and to ensure their alignment.
- const Type types[0];
-};
-
-// TypeSpecPtr points to the block of memory that holds
-// a TypeSpec struct, followed by an array of Type structs.
-// TypeSpecPtr is a managed pointer that knows how to delete
-// itself.
-using TypeSpecPtr = util::unique_cptr<TypeSpec>;
-
namespace {
// Builder that helps accumulate Type structs and then create a single
@@ -95,21 +57,22 @@
}
void AddType(const ResTable_type* type) {
- ResTable_config config;
- config.copyFromDtoH(type->config);
- types_.push_back(Type{config, type});
+ types_.push_back(type);
}
TypeSpecPtr Build() {
// Check for overflow.
- if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) {
+ using ElementType = const ResTable_type*;
+ if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
+ types_.size()) {
return {};
}
- TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
+ TypeSpec* type_spec =
+ (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
type_spec->type_spec = header_;
type_spec->idmap_entries = idmap_header_;
type_spec->type_count = types_.size();
- memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
+ memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
return TypeSpecPtr(type_spec);
}
@@ -118,7 +81,7 @@
const ResTable_typeSpec* header_;
const IdmapEntry_header* idmap_header_;
- std::vector<Type> types_;
+ std::vector<const ResTable_type*> types_;
};
} // namespace
@@ -162,18 +125,17 @@
return true;
}
-static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
- size_t entry_idx) {
+static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (entry_offset & 0x03) {
- LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned.";
+ LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
return false;
}
// Check that the offset doesn't overflow.
if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) {
// Overflow in offset.
- LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large.";
+ LOG(ERROR) << "Entry at offset " << entry_offset << " is too large.";
return false;
}
@@ -181,7 +143,7 @@
entry_offset += dtohl(type->entriesStart);
if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
- LOG(ERROR) << "Entry offset at index " << entry_idx
+ LOG(ERROR) << "Entry at offset " << entry_offset
<< " is too large. No room for ResTable_entry.";
return false;
}
@@ -191,13 +153,13 @@
const size_t entry_size = dtohs(entry->size);
if (entry_size < sizeof(*entry)) {
- LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
return false;
}
if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
- LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too large.";
return false;
}
@@ -205,7 +167,7 @@
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) {
- LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx
+ LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset
<< " for type " << (int)type->id << ".";
return false;
}
@@ -214,12 +176,12 @@
reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size);
const size_t value_size = dtohs(value->size);
if (value_size < sizeof(Res_value)) {
- LOG(ERROR) << "Res_value at index " << entry_idx << " is too small.";
+ LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small.";
return false;
}
if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
- LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx
+ LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset
<< " is too large.";
return false;
}
@@ -228,119 +190,76 @@
const size_t map_entry_count = dtohl(map->count);
size_t map_entries_start = entry_offset + entry_size;
if (map_entries_start & 0x03) {
- LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset.";
+ LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset.";
return false;
}
// Each entry is sizeof(ResTable_map) big.
if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) {
- LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << ".";
+ LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << ".";
return false;
}
}
return true;
}
-bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
- const ResTable_config& config, FindEntryResult* out_entry) const {
- const ResTable_config* best_config = nullptr;
- const ResTable_type* best_type = nullptr;
- uint32_t best_offset = 0;
+const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk,
+ uint16_t entry_index) {
+ uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index);
+ if (entry_offset == ResTable_type::NO_ENTRY) {
+ return nullptr;
+ }
+ return GetEntryFromOffset(type_chunk, entry_offset);
+}
- for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
- const Type* type = &type_spec_ptr->types[i];
- const ResTable_type* type_chunk = type->type;
+uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const size_t entry_count = dtohl(type_chunk->entryCount);
+ const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
- if (type->configuration.match(config) &&
- (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
- // The configuration matches and is better than the previous selection.
- // Find the entry value if it exists for this configuration.
- const size_t entry_count = dtohl(type_chunk->entryCount);
- const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+ // Check if there is the desired entry in this type.
- // Check if there is the desired entry in this type.
-
- if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
- // This is encoded as a sparse map, so perform a binary search.
- const ResTable_sparseTypeEntry* sparse_indices =
- reinterpret_cast<const ResTable_sparseTypeEntry*>(
- reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
- const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
- const ResTable_sparseTypeEntry* result =
- std::lower_bound(sparse_indices, sparse_indices_end, entry_idx,
- [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
- return dtohs(entry.idx) < entry_idx;
- });
-
- if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) {
- // No entry found.
- continue;
- }
-
- // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
- // the real offset divided by 4.
- best_offset = uint32_t{dtohs(result->offset)} * 4u;
- } else {
- if (entry_idx >= entry_count) {
- // This entry cannot be here.
- continue;
- }
-
- const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+ if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
+ // This is encoded as a sparse map, so perform a binary search.
+ const ResTable_sparseTypeEntry* sparse_indices =
+ reinterpret_cast<const ResTable_sparseTypeEntry*>(
reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
- const uint32_t offset = dtohl(entry_offsets[entry_idx]);
- if (offset == ResTable_type::NO_ENTRY) {
- continue;
- }
+ const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
+ const ResTable_sparseTypeEntry* result =
+ std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
+ [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
+ return dtohs(entry.idx) < entry_idx;
+ });
- // There is an entry for this resource, record it.
- best_offset = offset;
- }
-
- best_config = &type->configuration;
- best_type = type_chunk;
+ if (result == sparse_indices_end || dtohs(result->idx) != entry_index) {
+ // No entry found.
+ return ResTable_type::NO_ENTRY;
}
+
+ // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
+ // the real offset divided by 4.
+ return uint32_t{dtohs(result->offset)} * 4u;
}
- if (best_type == nullptr) {
- return false;
+ // This type is encoded as a dense array.
+ if (entry_index >= entry_count) {
+ // This entry cannot be here.
+ return ResTable_type::NO_ENTRY;
}
- if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) {
- return false;
- }
-
- const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
- reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
-
- const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
- out_entry->type_flags = dtohl(flags[entry_idx]);
- out_entry->entry = best_entry;
- out_entry->config = best_config;
- out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
- out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
- return true;
+ const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
+ return dtohl(entry_offsets[entry_index]);
}
-bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
- FindEntryResult* out_entry) const {
- ATRACE_CALL();
-
- // If the type IDs are offset in this package, we need to take that into account when searching
- // for a type.
- const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
- if (UNLIKELY(ptr == nullptr)) {
- return false;
+const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
+ uint32_t offset) {
+ if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) {
+ return nullptr;
}
-
- // If there is an IDMAP supplied with this package, translate the entry ID.
- if (ptr->idmap_entries != nullptr) {
- if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
- // There is no mapping, so the resource is not meant to be in this overlay package.
- return false;
- }
- }
- return FindEntry(ptr, entry_idx, config, out_entry);
+ return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
+ offset + dtohl(type_chunk->entriesStart));
}
void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
@@ -348,7 +267,7 @@
const static std::u16string kMipMap = u"mipmap";
const size_t type_count = type_specs_.size();
for (size_t i = 0; i < type_count; i++) {
- const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
+ const TypeSpecPtr& type_spec = type_specs_[i];
if (type_spec != nullptr) {
if (exclude_mipmap) {
const int type_idx = type_spec->type_spec->id - 1;
@@ -369,8 +288,11 @@
}
}
- for (size_t j = 0; j < type_spec->type_count; j++) {
- out_configs->insert(type_spec->types[j].configuration);
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ ResTable_config config;
+ config.copyFromDtoH((*iter)->config);
+ out_configs->insert(config);
}
}
}
@@ -380,10 +302,12 @@
char temp_locale[RESTABLE_MAX_LOCALE_LEN];
const size_t type_count = type_specs_.size();
for (size_t i = 0; i < type_count; i++) {
- const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
+ const TypeSpecPtr& type_spec = type_specs_[i];
if (type_spec != nullptr) {
- for (size_t j = 0; j < type_spec->type_count; j++) {
- const ResTable_config& configuration = type_spec->types[j].configuration;
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ ResTable_config configuration;
+ configuration.copyFromDtoH((*iter)->config);
if (configuration.locale != 0) {
configuration.getBcp47Locale(temp_locale, canonicalize);
std::string locale(temp_locale);
@@ -411,17 +335,17 @@
return 0u;
}
- for (size_t ti = 0; ti < type_spec->type_count; ti++) {
- const Type* type = &type_spec->types[ti];
- size_t entry_count = dtohl(type->type->entryCount);
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ const ResTable_type* type = *iter;
+ size_t entry_count = dtohl(type->entryCount);
for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
- reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
+ reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize));
const uint32_t offset = dtohl(entry_offsets[entry_idx]);
if (offset != ResTable_type::NO_ENTRY) {
- const ResTable_entry* entry =
- reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) +
- dtohl(type->type->entriesStart) + offset);
+ const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset);
if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
@@ -433,8 +357,7 @@
return 0u;
}
-const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
- const uint8_t package_id = get_package_id(resid);
+const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
for (const auto& loaded_package : packages_) {
if (loaded_package->GetPackageId() == package_id) {
return loaded_package.get();
@@ -486,14 +409,10 @@
util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
&loaded_package->package_name_);
- // A TypeSpec builder. We use this to accumulate the set of Types
- // available for a TypeSpec, and later build a single, contiguous block
- // of memory that holds all the Types together with the TypeSpec.
- std::unique_ptr<TypeSpecPtrBuilder> types_builder;
-
- // Keep track of the last seen type index. Since type IDs are 1-based,
- // this records their index, which is 0-based (type ID - 1).
- uint8_t last_type_idx = 0;
+ // A map of TypeSpec builders, each associated with an type index.
+ // We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
+ // contiguous block of memory that holds all the Types together with the TypeSpec.
+ std::unordered_map<int, std::unique_ptr<TypeSpecPtrBuilder>> type_builder_map;
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
@@ -527,28 +446,6 @@
case RES_TABLE_TYPE_SPEC_TYPE: {
ATRACE_NAME("LoadTableTypeSpec");
- // Starting a new TypeSpec, so finish the old one if there was one.
- if (types_builder) {
- TypeSpecPtr type_spec_ptr = types_builder->Build();
- if (type_spec_ptr == nullptr) {
- LOG(ERROR) << "Too many type configurations, overflow detected.";
- return {};
- }
-
- // We only add the type to the package if there is no IDMAP, or if the type is
- // overlaying something.
- if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
- // If this is an overlay, insert it at the target type ID.
- if (type_spec_ptr->idmap_entries != nullptr) {
- last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
- }
- loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
- }
-
- types_builder = {};
- last_type_idx = 0;
- }
-
const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
if (type_spec == nullptr) {
LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small.";
@@ -583,8 +480,6 @@
return {};
}
- last_type_idx = type_spec->id - 1;
-
// If this is an overlay, associate the mapping of this type to the target type
// from the IDMAP.
const IdmapEntry_header* idmap_entry_header = nullptr;
@@ -592,7 +487,13 @@
idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
}
- types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
+ std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1];
+ if (builder_ptr == nullptr) {
+ builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
+ } else {
+ LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
+ type_spec->id);
+ }
} break;
case RES_TABLE_TYPE_TYPE: {
@@ -607,12 +508,15 @@
}
// Type chunks must be preceded by their TypeSpec chunks.
- if (!types_builder || type->id - 1 != last_type_idx) {
- LOG(ERROR) << "RES_TABLE_TYPE_TYPE found without preceding RES_TABLE_TYPE_SPEC_TYPE.";
+ std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1];
+ if (builder_ptr != nullptr) {
+ builder_ptr->AddType(type);
+ } else {
+ LOG(ERROR) << StringPrintf(
+ "RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
+ type->id);
return {};
}
-
- types_builder->AddType(type);
} break;
case RES_TABLE_LIBRARY_TYPE: {
@@ -638,7 +542,7 @@
arraysize(entry_iter->packageName), &package_name);
if (dtohl(entry_iter->packageId) >= std::numeric_limits<uint8_t>::max()) {
- LOG(ERROR) << base::StringPrintf(
+ LOG(ERROR) << StringPrintf(
"Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.",
dtohl(entry_iter->packageId), package_name.c_str());
return {};
@@ -651,14 +555,20 @@
} break;
default:
- LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type());
break;
}
}
- // Finish the last TypeSpec.
- if (types_builder) {
- TypeSpecPtr type_spec_ptr = types_builder->Build();
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return {};
+ }
+
+ // Flatten and construct the TypeSpecs.
+ for (auto& entry : type_builder_map) {
+ uint8_t type_idx = static_cast<uint8_t>(entry.first);
+ TypeSpecPtr type_spec_ptr = entry.second->Build();
if (type_spec_ptr == nullptr) {
LOG(ERROR) << "Too many type configurations, overflow detected.";
return {};
@@ -669,40 +579,15 @@
if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
// If this is an overlay, insert it at the target type ID.
if (type_spec_ptr->idmap_entries != nullptr) {
- last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
+ type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
}
- loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+ loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr);
}
}
- if (iter.HadError()) {
- LOG(ERROR) << iter.GetLastError();
- return {};
- }
return std::move(loaded_package);
}
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
- FindEntryResult* out_entry) const {
- ATRACE_CALL();
-
- const uint8_t package_id = get_package_id(resid);
- const uint8_t type_id = get_type_id(resid);
- const uint16_t entry_id = get_entry_id(resid);
-
- if (UNLIKELY(type_id == 0)) {
- LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
- return false;
- }
-
- for (const auto& loaded_package : packages_) {
- if (loaded_package->GetPackageId() == package_id) {
- return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry);
- }
- }
- return false;
-}
-
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
bool load_as_shared_library) {
ATRACE_CALL();
@@ -752,7 +637,7 @@
} break;
default:
- LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type());
break;
}
}
@@ -784,7 +669,7 @@
break;
default:
- LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type());
break;
}
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b033137..ef08897 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -69,6 +69,8 @@
Entry entries[0];
};
+struct FindEntryResult;
+
// AssetManager2 is the main entry point for accessing assets and resources.
// AssetManager2 provides caching of resources retrieved via the underlying ApkAssets.
class AssetManager2 {
@@ -127,7 +129,7 @@
// If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap'
// will be excluded from the list.
std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false,
- bool exclude_mipmap = false);
+ bool exclude_mipmap = false) const;
// Returns all the locales for which there are resources defined. This includes resource
// locales in all the ApkAssets set for this AssetManager.
@@ -136,24 +138,24 @@
// If `merge_equivalent_languages` is set to true, resource locales will be canonicalized
// and de-duped in the resulting list.
std::set<std::string> GetResourceLocales(bool exclude_system = false,
- bool merge_equivalent_languages = false);
+ bool merge_equivalent_languages = false) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found located
// in the assets/ directory.
// `mode` controls how the file is opened.
//
// NOTE: The loaded APKs are searched in reverse order.
- std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode);
+ std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const;
// Opens a file within the assets/ directory of the APK specified by `cookie`.
// `mode` controls how the file is opened.
std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
- Asset::AccessMode mode);
+ Asset::AccessMode mode) const;
// Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
// of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
// The entries are sorted by their ASCII name.
- std::unique_ptr<AssetDir> OpenDir(const std::string& dirname);
+ std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found.
// `mode` controls how the file is opened.
@@ -161,24 +163,24 @@
//
// NOTE: The loaded APKs are searched in reverse order.
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode,
- ApkAssetsCookie* out_cookie = nullptr);
+ ApkAssetsCookie* out_cookie = nullptr) const;
// Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened.
// This is typically used to open a specific AndroidManifest.xml, or a binary XML file
// referenced by a resource lookup with GetResource().
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
- Asset::AccessMode mode);
+ Asset::AccessMode mode) const;
// Populates the `out_name` parameter with resource name information.
// Utf8 strings are preferred, and only if they are unavailable are
// the Utf16 variants populated.
// Returns false if the resource was not found or the name was missing/corrupt.
- bool GetResourceName(uint32_t resid, ResourceName* out_name);
+ bool GetResourceName(uint32_t resid, ResourceName* out_name) const;
// Populates `out_flags` with the bitmask of configuration axis that this resource varies with.
// See ResTable_config for the list of configuration axis.
// Returns false if the resource was not found.
- bool GetResourceFlags(uint32_t resid, uint32_t* out_flags);
+ bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const;
// Finds the resource ID assigned to `resource_name`.
// `resource_name` must be of the form '[package:][type/]entry'.
@@ -186,7 +188,7 @@
// If no type is specified in `resource_name`, then `fallback_type` is used as the type.
// Returns 0x0 if no resource by that name was found.
uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {},
- const std::string& fallback_package = {});
+ const std::string& fallback_package = {}) const;
// Retrieves the best matching resource with ID `resid`. The resource value is filled into
// `out_value` and the configuration for the selected value is populated in `out_selected_config`.
@@ -199,7 +201,7 @@
// this function logs if the resource was a map/bag type before returning kInvalidCookie.
ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override,
Res_value* out_value, ResTable_config* out_selected_config,
- uint32_t* out_flags);
+ uint32_t* out_flags) const;
// Resolves the resource reference in `in_out_value` if the data type is
// Res_value::TYPE_REFERENCE.
@@ -215,7 +217,7 @@
// it was not found.
ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
- uint32_t* out_last_reference);
+ uint32_t* out_last_reference) const;
// Retrieves the best matching bag/map resource with ID `resid`.
// This method will resolve all parent references for this bag and merge keys with the child.
@@ -233,9 +235,9 @@
std::unique_ptr<Theme> NewTheme();
template <typename Func>
- void ForEachPackage(Func func) {
+ void ForEachPackage(Func func) const {
for (const PackageGroup& package_group : package_groups_) {
- func(package_group.packages_.front()->GetPackageName(),
+ func(package_group.packages_.front().loaded_package_->GetPackageName(),
package_group.dynamic_ref_table.mAssignedPackageId);
}
}
@@ -260,7 +262,7 @@
// NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
// bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
- FindEntryResult* out_entry);
+ FindEntryResult* out_entry) const;
// Assigns package IDs to all shared library ApkAssets.
// Should be called whenever the ApkAssets are changed.
@@ -270,13 +272,43 @@
// bitmask `diff`.
void InvalidateCaches(uint32_t diff);
+ // Triggers the re-construction of lists of types that match the set configuration.
+ // This should always be called when mutating the AssetManager's configuration or ApkAssets set.
+ void RebuildFilterList();
+
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
std::vector<const ApkAssets*> apk_assets_;
+ // A collection of configurations and their associated ResTable_type that match the current
+ // AssetManager configuration.
+ struct FilteredConfigGroup {
+ std::vector<ResTable_config> configurations;
+ std::vector<const ResTable_type*> types;
+ };
+
+ // Represents an single package.
+ struct ConfiguredPackage {
+ // A pointer to the immutable, loaded package info.
+ const LoadedPackage* loaded_package_;
+
+ // A mutable AssetManager-specific list of configurations that match the AssetManager's
+ // current configuration. This is used as an optimization to avoid checking every single
+ // candidate configuration when looking up resources.
+ ByteBucketArray<FilteredConfigGroup> filtered_configs_;
+ };
+
+ // Represents a logical package, which can be made up of many individual packages. Each package
+ // in a PackageGroup shares the same package name and package ID.
struct PackageGroup {
- std::vector<const LoadedPackage*> packages_;
+ // The set of packages that make-up this group.
+ std::vector<ConfiguredPackage> packages_;
+
+ // The cookies associated with each package in the group. They share the same order as
+ // packages_.
std::vector<ApkAssetsCookie> cookies_;
+
+ // A library reference table that contains build-package ID to runtime-package ID mappings.
DynamicRefTable dynamic_ref_table;
};
@@ -350,7 +382,7 @@
ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config = nullptr,
uint32_t* in_out_type_spec_flags = nullptr,
- uint32_t* out_last_ref = nullptr);
+ uint32_t* out_last_ref = nullptr) const;
private:
DISALLOW_COPY_AND_ASSIGN(Theme);
diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h
index f281921..03fad49 100644
--- a/libs/androidfw/include/androidfw/AttributeFinder.h
+++ b/libs/androidfw/include/androidfw/AttributeFinder.h
@@ -58,6 +58,7 @@
BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end);
Iterator Find(uint32_t attr);
+ inline Iterator end();
private:
void JumpToClosestAttribute(uint32_t package_id);
@@ -201,6 +202,11 @@
return end_;
}
+template <typename Derived, typename Iterator>
+Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() {
+ return end_;
+}
+
} // namespace android
#endif // ANDROIDFW_ATTRIBUTE_FINDER_H
diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h
index 69b76041..35ef98d 100644
--- a/libs/androidfw/include/androidfw/AttributeResolution.h
+++ b/libs/androidfw/include/androidfw/AttributeResolution.h
@@ -17,7 +17,8 @@
#ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H
#define ANDROIDFW_ATTRIBUTERESOLUTION_H
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
namespace android {
@@ -42,19 +43,19 @@
// `out_values` must NOT be nullptr.
// `out_indices` may be nullptr.
-bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid,
uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
// `out_values` must NOT be nullptr.
// `out_indices` is NOT optional and must NOT be nullptr.
-void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
- uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
uint32_t* out_values, uint32_t* out_indices);
// `out_values` must NOT be nullptr.
// `out_indices` may be nullptr.
-bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
+bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
} // namespace android
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 965e2db..35ae5fc 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -41,32 +41,40 @@
int package_id = 0;
};
-struct FindEntryResult {
- // A pointer to the resource table entry for this resource.
- // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
- // a ResTable_map_entry and processed as a bag/map.
- const ResTable_entry* entry = nullptr;
+// TypeSpec is going to be immediately proceeded by
+// an array of Type structs, all in the same block of memory.
+struct TypeSpec {
+ // Pointer to the mmapped data where flags are kept.
+ // Flags denote whether the resource entry is public
+ // and under which configurations it varies.
+ const ResTable_typeSpec* type_spec;
- // The configuration for which the resulting entry was defined.
- const ResTable_config* config = nullptr;
+ // Pointer to the mmapped data where the IDMAP mappings for this type
+ // exist. May be nullptr if no IDMAP exists.
+ const IdmapEntry_header* idmap_entries;
- // Stores the resulting bitmask of configuration axis with which the resource value varies.
- uint32_t type_flags = 0u;
+ // The number of types that follow this struct.
+ // There is a type for each configuration that entries are defined for.
+ size_t type_count;
- // The dynamic package ID map for the package from which this resource came from.
- const DynamicRefTable* dynamic_ref_table = nullptr;
+ // Trick to easily access a variable number of Type structs
+ // proceeding this struct, and to ensure their alignment.
+ const ResTable_type* types[0];
- // The string pool reference to the type's name. This uses a different string pool than
- // the global string pool, but this is hidden from the caller.
- StringPoolRef type_string_ref;
+ inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const {
+ if (entry_index >= dtohl(type_spec->entryCount)) {
+ return 0u;
+ }
- // The string pool reference to the entry's name. This uses a different string pool than
- // the global string pool, but this is hidden from the caller.
- StringPoolRef entry_string_ref;
+ const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1);
+ return flags[entry_index];
+ }
};
-struct TypeSpec;
-class LoadedArsc;
+// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of
+// ResTable_type pointers.
+// TypeSpecPtr is a managed pointer that knows how to delete itself.
+using TypeSpecPtr = util::unique_cptr<TypeSpec>;
class LoadedPackage {
public:
@@ -76,9 +84,6 @@
~LoadedPackage();
- bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
- FindEntryResult* out_entry) const;
-
// Finds the entry with the specified type name and entry name. The names are in UTF-16 because
// the underlying ResStringPool API expects this. For now this is acceptable, but since
// the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
@@ -86,6 +91,12 @@
// for patching the correct package ID to the resource ID.
uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
+ static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index);
+
+ static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index);
+
+ static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset);
+
// Returns the string pool where type names are stored.
inline const ResStringPool* GetTypeStringPool() const {
return &type_string_pool_;
@@ -135,14 +146,32 @@
// before being inserted into the set. This may cause some equivalent locales to de-dupe.
void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
+ // type_idx is TT - 1 from 0xPPTTEEEE.
+ inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
+ // If the type IDs are offset in this package, we need to take that into account when searching
+ // for a type.
+ return type_specs_[type_index - type_id_offset_].get();
+ }
+
+ template <typename Func>
+ void ForEachTypeSpec(Func f) const {
+ for (size_t i = 0; i < type_specs_.size(); i++) {
+ const TypeSpecPtr& ptr = type_specs_[i];
+ if (ptr != nullptr) {
+ uint8_t type_id = ptr->type_spec->id;
+ if (ptr->idmap_entries != nullptr) {
+ type_id = ptr->idmap_entries->target_type_id;
+ }
+ f(ptr.get(), type_id - 1);
+ }
+ }
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
LoadedPackage();
- bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
- const ResTable_config& config, FindEntryResult* out_entry) const;
-
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
std::string package_name_;
@@ -152,7 +181,7 @@
bool system_ = false;
bool overlay_ = false;
- ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
+ ByteBucketArray<TypeSpecPtr> type_specs_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
};
@@ -180,25 +209,20 @@
return &global_string_pool_;
}
- // Finds the resource with ID `resid` with the best value for configuration `config`.
- // The parameter `out_entry` will be filled with the resulting resource entry.
- // The resource entry can be a simple entry (ResTable_entry) or a complex bag
- // (ResTable_entry_map).
- bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const;
-
- // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
- const LoadedPackage* GetPackageForId(uint32_t resid) const;
-
- // Returns true if this is a system provided resource.
- inline bool IsSystem() const {
- return system_;
- }
+ // Gets a pointer to the package with the specified package ID, or nullptr if no such package
+ // exists.
+ const LoadedPackage* GetPackageById(uint8_t package_id) const;
// Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
return packages_;
}
+ // Returns true if this is a system provided resource.
+ inline bool IsSystem() const {
+ return system_;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
new file mode 100644
index 0000000..64924f4
--- /dev/null
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_MUTEXGUARD_H
+#define ANDROIDFW_MUTEXGUARD_H
+
+#include <mutex>
+#include <type_traits>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+template <typename T>
+class ScopedLock;
+
+// Owns the guarded object and protects access to it via a mutex.
+// The guarded object is inaccessible via this class.
+// The mutex is locked and the object accessed via the ScopedLock<T> class.
+//
+// NOTE: The template parameter T should not be a raw pointer, since ownership
+// is ambiguous and error-prone. Instead use an std::unique_ptr<>.
+//
+// Example use:
+//
+// Guarded<std::string> shared_string("hello");
+// {
+// ScopedLock<std::string> locked_string(shared_string);
+// *locked_string += " world";
+// }
+//
+template <typename T>
+class Guarded {
+ static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
+
+ public:
+ explicit Guarded() : guarded_() {
+ }
+
+ template <typename U = T>
+ explicit Guarded(const T& guarded,
+ typename std::enable_if<std::is_copy_constructible<U>::value>::type = void())
+ : guarded_(guarded) {
+ }
+
+ template <typename U = T>
+ explicit Guarded(T&& guarded,
+ typename std::enable_if<std::is_move_constructible<U>::value>::type = void())
+ : guarded_(std::move(guarded)) {
+ }
+
+ private:
+ friend class ScopedLock<T>;
+
+ DISALLOW_COPY_AND_ASSIGN(Guarded);
+
+ std::mutex lock_;
+ T guarded_;
+};
+
+template <typename T>
+class ScopedLock {
+ public:
+ explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) {
+ }
+
+ T& operator*() {
+ return guarded_;
+ }
+
+ T* operator->() {
+ return &guarded_;
+ }
+
+ T* get() {
+ return &guarded_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedLock);
+
+ std::lock_guard<std::mutex> lock_;
+ T& guarded_;
+};
+
+} // namespace android
+
+#endif // ANDROIDFW_MUTEXGUARD_H
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index c2eae85..d94779b 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -28,7 +28,7 @@
StringPiece* out_entry);
inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) {
- return resid | (static_cast<uint32_t>(package_id) << 24);
+ return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24);
}
inline uint8_t get_package_id(uint32_t resid) {
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 6c43a67..e2b9f00 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -26,58 +26,56 @@
using ::android::base::unique_fd;
using ::com::android::basic::R;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
namespace android {
TEST(ApkAssetsTest, LoadApk) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
-
- const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
- ASSERT_NE(nullptr, loaded_package);
-
- std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
- ASSERT_NE(nullptr, asset);
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
+ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
}
TEST(ApkAssetsTest, LoadApkFromFd) {
const std::string path = GetTestDataPath() + "/basic/basic.apk";
unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
- ASSERT_GE(fd.get(), 0);
+ ASSERT_THAT(fd.get(), Ge(0));
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/);
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
-
- const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
- ASSERT_NE(nullptr, loaded_package);
-
- std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
- ASSERT_NE(nullptr, asset);
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
+ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
}
TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
+
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
- ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic());
loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
- ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
}
@@ -86,19 +84,22 @@
ResTable target_table;
const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+ ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
+ Eq(NO_ERROR));
ResTable overlay_table;
const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+ ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
+ Eq(NO_ERROR));
util::unique_cptr<void> idmap_data;
void* temp_data;
size_t idmap_len;
- ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
- overlay_path.c_str(), &temp_data, &idmap_len));
+ ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
+ overlay_path.c_str(), &temp_data, &idmap_len),
+ Eq(NO_ERROR));
idmap_data.reset(temp_data);
TemporaryFile tf;
@@ -108,37 +109,30 @@
// Open something so that the destructor of TemporaryFile closes a valid fd.
tf.fd = open("/dev/null", O_WRONLY);
- std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
- ASSERT_NE(nullptr, loaded_overlay_apk);
+ ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull());
}
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
- {
- std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
- ASSERT_NE(nullptr, assets);
- }
+ { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
- {
- std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
- ASSERT_NE(nullptr, assets);
- }
+ { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
}
TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
- ASSERT_NE(nullptr, asset);
+ ASSERT_THAT(asset, NotNull());
off64_t start, length;
unique_fd fd(asset->openFileDescriptor(&start, &length));
- EXPECT_GE(fd.get(), 0);
+ ASSERT_THAT(fd.get(), Ge(0));
lseek64(fd.get(), start, SEEK_SET);
@@ -146,7 +140,7 @@
buffer.resize(length);
ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length));
- EXPECT_EQ("This should be uncompressed.\n\n", buffer);
+ EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n"));
}
} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 85e8f25..437e147 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -81,17 +81,18 @@
}
BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld);
-static void BM_AssetManagerGetResource(benchmark::State& state) {
- GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
- basic::R::integer::number1, state);
+static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) {
+ GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state);
}
-BENCHMARK(BM_AssetManagerGetResource);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref);
-static void BM_AssetManagerGetResourceOld(benchmark::State& state) {
- GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
- basic::R::integer::number1, state);
+static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) {
+ GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid,
+ state);
}
-BENCHMARK(BM_AssetManagerGetResourceOld);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref);
static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
GetResourceBenchmark(
@@ -196,7 +197,7 @@
static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) {
AssetManager assets;
if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/,
- false /*isSystemAssets*/)) {
+ true /*isSystemAssets*/)) {
state.SkipWithError("Failed to load assets");
return;
}
@@ -211,4 +212,44 @@
}
BENCHMARK(BM_AssetManagerGetResourceLocalesOld);
+static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+
+ while (state.KeepRunning()) {
+ config.sdkVersion = ~config.sdkVersion;
+ assets.SetConfiguration(config);
+ }
+}
+BENCHMARK(BM_AssetManagerSetConfigurationFramework);
+
+static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/,
+ true /*isSystemAssets*/)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& table = assets.getResources(true);
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+
+ while (state.KeepRunning()) {
+ config.sdkVersion = ~config.sdkVersion;
+ assets.setConfiguration(config);
+ }
+}
+BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld);
+
} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 92462a6..7cac2b3 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -36,6 +36,10 @@
namespace lib_two = com::android::lib_two;
namespace libclient = com::android::libclient;
+using ::testing::Eq;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
namespace android {
class AssetManager2Test : public ::testing::Test {
@@ -59,11 +63,14 @@
libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk");
ASSERT_NE(nullptr, libclient_assets_);
- appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
+ appaslib_assets_ = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
ASSERT_NE(nullptr, appaslib_assets_);
system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/);
ASSERT_NE(nullptr, system_assets_);
+
+ app_assets_ = ApkAssets::Load(GetTestDataPath() + "/app/app.apk");
+ ASSERT_THAT(app_assets_, NotNull());
}
protected:
@@ -75,6 +82,7 @@
std::unique_ptr<const ApkAssets> libclient_assets_;
std::unique_ptr<const ApkAssets> appaslib_assets_;
std::unique_ptr<const ApkAssets> system_assets_;
+ std::unique_ptr<const ApkAssets> app_assets_;
};
TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
@@ -233,6 +241,25 @@
assetmanager.SetApkAssets(
{lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+ const ResolvedBag* bag = assetmanager.GetBag(fix_package_id(lib_one::R::style::Theme, 0x03));
+ ASSERT_NE(nullptr, bag);
+ ASSERT_GE(bag->entry_count, 2u);
+
+ // First two attributes come from lib_one.
+ EXPECT_EQ(1, bag->entries[0].cookie);
+ EXPECT_EQ(0x03, get_package_id(bag->entries[0].key));
+ EXPECT_EQ(1, bag->entries[1].cookie);
+ EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
+}
+
+TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) {
+ AssetManager2 assetmanager;
+
+ // libclient is built with lib_one and then lib_two in order.
+ // Reverse the order to test that proper package ID re-assignment is happening.
+ assetmanager.SetApkAssets(
+ {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme);
ASSERT_NE(nullptr, bag);
ASSERT_GE(bag->entry_count, 2u);
@@ -446,8 +473,68 @@
assetmanager.GetResourceId("main", "layout", "com.android.basic"));
}
-TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {}
+TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({system_assets_.get()});
-TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
+ std::unique_ptr<Asset> asset = assetmanager.Open("file.txt", Asset::ACCESS_BUFFER);
+ ASSERT_THAT(asset, NotNull());
+
+ const char* data = reinterpret_cast<const char*>(asset->getBuffer(false /*wordAligned*/));
+ ASSERT_THAT(data, NotNull());
+ EXPECT_THAT(std::string(data, asset->getLength()), StrEq("file\n"));
+}
+
+TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({system_assets_.get(), app_assets_.get()});
+
+ std::unique_ptr<Asset> asset = assetmanager.Open("file.txt", Asset::ACCESS_BUFFER);
+ ASSERT_THAT(asset, NotNull());
+
+ const char* data = reinterpret_cast<const char*>(asset->getBuffer(false /*wordAligned*/));
+ ASSERT_THAT(data, NotNull());
+ EXPECT_THAT(std::string(data, asset->getLength()), StrEq("app override file\n"));
+}
+
+TEST_F(AssetManager2Test, OpenDir) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({system_assets_.get()});
+
+ std::unique_ptr<AssetDir> asset_dir = assetmanager.OpenDir("");
+ ASSERT_THAT(asset_dir, NotNull());
+ ASSERT_THAT(asset_dir->getFileCount(), Eq(2u));
+
+ EXPECT_THAT(asset_dir->getFileName(0), Eq(String8("file.txt")));
+ EXPECT_THAT(asset_dir->getFileType(0), Eq(FileType::kFileTypeRegular));
+
+ EXPECT_THAT(asset_dir->getFileName(1), Eq(String8("subdir")));
+ EXPECT_THAT(asset_dir->getFileType(1), Eq(FileType::kFileTypeDirectory));
+
+ asset_dir = assetmanager.OpenDir("subdir");
+ ASSERT_THAT(asset_dir, NotNull());
+ ASSERT_THAT(asset_dir->getFileCount(), Eq(1u));
+
+ EXPECT_THAT(asset_dir->getFileName(0), Eq(String8("subdir_file.txt")));
+ EXPECT_THAT(asset_dir->getFileType(0), Eq(FileType::kFileTypeRegular));
+}
+
+TEST_F(AssetManager2Test, OpenDirFromManyApks) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({system_assets_.get(), app_assets_.get()});
+
+ std::unique_ptr<AssetDir> asset_dir = assetmanager.OpenDir("");
+ ASSERT_THAT(asset_dir, NotNull());
+ ASSERT_THAT(asset_dir->getFileCount(), Eq(3u));
+
+ EXPECT_THAT(asset_dir->getFileName(0), Eq(String8("app_file.txt")));
+ EXPECT_THAT(asset_dir->getFileType(0), Eq(FileType::kFileTypeRegular));
+
+ EXPECT_THAT(asset_dir->getFileName(1), Eq(String8("file.txt")));
+ EXPECT_THAT(asset_dir->getFileType(1), Eq(FileType::kFileTypeRegular));
+
+ EXPECT_THAT(asset_dir->getFileName(2), Eq(String8("subdir")));
+ EXPECT_THAT(asset_dir->getFileType(2), Eq(FileType::kFileTypeDirectory));
+}
} // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
new file mode 100644
index 0000000..fa300c5
--- /dev/null
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "benchmark/benchmark.h"
+
+//#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "BenchmarkHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace app = com::android::app;
+namespace basic = com::android::basic;
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr const static uint32_t Theme_Material_Light = 0x01030237u;
+
+static void BM_ApplyStyle(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> styles_apk =
+ ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ if (styles_apk == nullptr) {
+ state.SkipWithError("failed to load assets");
+ return;
+ }
+
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({styles_apk.get()});
+
+ std::unique_ptr<Asset> asset =
+ assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ state.SkipWithError("failed to load layout");
+ return;
+ }
+
+ ResXMLTree xml_tree;
+ if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) {
+ state.SkipWithError("corrupt xml layout");
+ return;
+ }
+
+ // Skip to the first tag.
+ while (xml_tree.next() != ResXMLParser::START_TAG) {
+ }
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ theme->ApplyStyle(app::R::style::StyleTwo);
+
+ std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two,
+ app::R::attr::attr_three, app::R::attr::attr_four,
+ app::R::attr::attr_five, app::R::attr::attr_empty}};
+ std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+ std::array<uint32_t, attrs.size() + 1> indices;
+
+ while (state.KeepRunning()) {
+ ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(),
+ attrs.size(), values.data(), indices.data());
+ }
+}
+BENCHMARK(BM_ApplyStyle);
+
+static void BM_ApplyStyleFramework(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath);
+ if (framework_apk == nullptr) {
+ state.SkipWithError("failed to load framework assets");
+ return;
+ }
+
+ std::unique_ptr<const ApkAssets> basic_apk =
+ ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ if (basic_apk == nullptr) {
+ state.SkipWithError("failed to load assets");
+ return;
+ }
+
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()});
+
+ ResTable_config device_config;
+ memset(&device_config, 0, sizeof(device_config));
+ device_config.language[0] = 'e';
+ device_config.language[1] = 'n';
+ device_config.country[0] = 'U';
+ device_config.country[1] = 'S';
+ device_config.orientation = ResTable_config::ORIENTATION_PORT;
+ device_config.smallestScreenWidthDp = 700;
+ device_config.screenWidthDp = 700;
+ device_config.screenHeightDp = 1024;
+ device_config.sdkVersion = 27;
+
+ Res_value value;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/,
+ 0u /*density_override*/, &value, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ state.SkipWithError("failed to find R.layout.layout");
+ return;
+ }
+
+ size_t len = 0u;
+ const char* layout_path =
+ assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len);
+ if (layout_path == nullptr || len == 0u) {
+ state.SkipWithError("failed to lookup layout path");
+ return;
+ }
+
+ std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(
+ StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ state.SkipWithError("failed to load layout");
+ return;
+ }
+
+ ResXMLTree xml_tree;
+ if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) {
+ state.SkipWithError("corrupt xml layout");
+ return;
+ }
+
+ // Skip to the first tag.
+ while (xml_tree.next() != ResXMLParser::START_TAG) {
+ }
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ theme->ApplyStyle(Theme_Material_Light);
+
+ std::array<uint32_t, 92> attrs{
+ {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099,
+ 0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f,
+ 0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151,
+ 0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158,
+ 0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f,
+ 0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166,
+ 0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d,
+ 0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d,
+ 0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5,
+ 0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f,
+ 0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d,
+ 0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df,
+ 0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9,
+ 0x011100ca}};
+
+ std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+ std::array<uint32_t, attrs.size() + 1> indices;
+ while (state.KeepRunning()) {
+ ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/,
+ attrs.data(), attrs.size(), values.data(), indices.data());
+ }
+}
+BENCHMARK(BM_ApplyStyleFramework);
+
+} // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index 2d73ce8..c8dbe20 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -21,6 +21,8 @@
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/macros.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceUtils.h"
#include "TestHelpers.h"
#include "data/styles/R.h"
@@ -32,15 +34,14 @@
class AttributeResolutionTest : public ::testing::Test {
public:
virtual void SetUp() override {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(
- GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(),
- 1 /*cookie*/, true /*copyData*/));
+ styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, styles_assets_);
+ assetmanager_.SetApkAssets({styles_assets_.get()});
}
protected:
- ResTable table_;
+ std::unique_ptr<const ApkAssets> styles_assets_;
+ AssetManager2 assetmanager_;
};
class AttributeResolutionXmlTest : public AttributeResolutionTest {
@@ -48,13 +49,12 @@
virtual void SetUp() override {
AttributeResolutionTest::SetUp();
- std::string contents;
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk",
- "res/layout/layout.xml", &contents));
+ std::unique_ptr<Asset> asset =
+ assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
+ ASSERT_NE(nullptr, asset);
- ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(),
- true /*copyData*/));
+ ASSERT_EQ(NO_ERROR,
+ xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/));
// Skip to the first tag.
while (xml_parser_.next() != ResXMLParser::START_TAG) {
@@ -65,15 +65,50 @@
ResXMLTree xml_parser_;
};
+TEST(AttributeResolutionLibraryTest, ApplyStyleWithDefaultStyleResId) {
+ AssetManager2 assetmanager;
+ auto apk_assets = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, apk_assets);
+ assetmanager.SetApkAssets({apk_assets.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+
+ std::array<uint32_t, 2> attrs{
+ {fix_package_id(R::attr::attr_one, 0x02), fix_package_id(R::attr::attr_two, 0x02)}};
+ std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+ std::array<uint32_t, attrs.size() + 1> indices;
+ ApplyStyle(theme.get(), nullptr /*xml_parser*/, 0u /*def_style_attr*/,
+ fix_package_id(R::style::StyleOne, 0x02), attrs.data(), attrs.size(), values.data(),
+ indices.data());
+
+ const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
+
+ const uint32_t* values_cursor = values.data();
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(1u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(2u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+}
+
TEST_F(AttributeResolutionTest, Theme) {
- ResTable::Theme theme(table_);
- ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+ std::unique_ptr<Theme> theme = assetmanager_.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo));
std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
R::attr::attr_four, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
- ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/,
+ ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/,
nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(),
attrs.size(), values.data(), nullptr /*out_indices*/));
@@ -126,8 +161,8 @@
R::attr::attr_four, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
- ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(),
- nullptr /*out_indices*/));
+ ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(),
+ values.data(), nullptr /*out_indices*/));
uint32_t* values_cursor = values.data();
EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
@@ -171,15 +206,15 @@
}
TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) {
- ResTable::Theme theme(table_);
- ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+ std::unique_ptr<Theme> theme = assetmanager_.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo));
std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
std::array<uint32_t, attrs.size() + 1> indices;
- ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(),
+ ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(),
attrs.size(), values.data(), indices.data());
const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 7149bee..faddfe5 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -33,19 +33,21 @@
}
}
+ // Make sure to force creation of the ResTable first, or else the configuration doesn't get set.
+ const ResTable& table = assetmanager.getResources(true);
if (config != nullptr) {
assetmanager.setConfiguration(*config);
}
- const ResTable& table = assetmanager.getResources(true);
-
Res_value value;
ResTable_config selected_config;
uint32_t flags;
+ uint32_t last_ref = 0u;
while (state.KeepRunning()) {
- table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
- &selected_config);
+ ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
+ &selected_config);
+ table.resolveReference(&value, block, &last_ref, &flags, &selected_config);
}
}
@@ -72,10 +74,12 @@
Res_value value;
ResTable_config selected_config;
uint32_t flags;
+ uint32_t last_id = 0u;
while (state.KeepRunning()) {
- assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value,
- &selected_config, &flags);
+ ApkAssetsCookie cookie = assetmanager.GetResource(
+ resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags);
+ assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id);
}
}
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 37ddafb..cae632d 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -16,6 +16,9 @@
#include "androidfw/LoadedArsc.h"
+#include "android-base/file.h"
+#include "androidfw/ResourceUtils.h"
+
#include "TestHelpers.h"
#include "data/basic/R.h"
#include "data/libclient/R.h"
@@ -27,6 +30,14 @@
namespace libclient = com::android::libclient;
namespace sparse = com::android::sparse;
+using ::android::base::ReadFileToString;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
namespace android {
TEST(LoadedArscTest, LoadSinglePackageArsc) {
@@ -35,39 +46,24 @@
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
- const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
- EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName());
- EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one));
+ ASSERT_THAT(package, NotNull());
+ EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app"));
+ EXPECT_THAT(package->GetPackageId(), Eq(0x7f));
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 24;
+ const uint8_t type_index = get_type_id(app::R::string::string_one) - 1;
+ const uint16_t entry_index = get_entry_id(app::R::string::string_one);
- FindEntryResult entry;
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
- ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
- ASSERT_NE(nullptr, entry.entry);
-}
-
-TEST(LoadedArscTest, FindDefaultEntry) {
- std::string contents;
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
-
- ResTable_config desired_config;
- memset(&desired_config, 0, sizeof(desired_config));
- desired_config.language[0] = 'd';
- desired_config.language[1] = 'e';
-
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
- ASSERT_NE(nullptr, entry.entry);
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
}
TEST(LoadedArscTest, LoadSparseEntryApp) {
@@ -76,15 +72,22 @@
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+ ASSERT_THAT(package, NotNull());
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry));
- ASSERT_NE(nullptr, entry.entry);
+ const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+ const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
}
TEST(LoadedArscTest, LoadSharedLibrary) {
@@ -93,14 +96,13 @@
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
const auto& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
-
+ ASSERT_THAT(packages, SizeIs(1u));
EXPECT_TRUE(packages[0]->IsDynamic());
- EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName());
- EXPECT_EQ(0, packages[0]->GetPackageId());
+ EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one"));
+ EXPECT_THAT(packages[0]->GetPackageId(), Eq(0));
const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
@@ -114,25 +116,23 @@
"resources.arsc", &contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
const auto& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
-
+ ASSERT_THAT(packages, SizeIs(1u));
EXPECT_FALSE(packages[0]->IsDynamic());
- EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName());
- EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+ EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient"));
+ EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
// The library has two dependencies.
- ASSERT_EQ(2u, dynamic_pkg_map.size());
+ ASSERT_THAT(dynamic_pkg_map, SizeIs(2u));
+ EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one"));
+ EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02));
- EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name);
- EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id);
-
- EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name);
- EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id);
+ EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two"));
+ EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03));
}
TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
@@ -143,13 +143,12 @@
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
true /*load_as_shared_library*/);
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
const auto& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
-
+ ASSERT_THAT(packages, SizeIs(1u));
EXPECT_TRUE(packages[0]->IsDynamic());
- EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+ EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
}
TEST(LoadedArscTest, LoadFeatureSplit) {
@@ -157,21 +156,67 @@
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
- ResTable_config desired_config;
- memset(&desired_config, 0, sizeof(desired_config));
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3));
+ ASSERT_THAT(package, NotNull());
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
+ uint8_t type_index = get_type_id(basic::R::string::test3) - 1;
+ uint8_t entry_index = get_entry_id(basic::R::string::test3);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+ ASSERT_THAT(type_spec->types[0], NotNull());
size_t len;
- const char16_t* type_name16 = entry.type_string_ref.string16(&len);
- ASSERT_NE(nullptr, type_name16);
- ASSERT_NE(0u, len);
+ const char16_t* type_name16 =
+ package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len);
+ ASSERT_THAT(type_name16, NotNull());
+ EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string"));
- std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
- EXPECT_EQ(std::string("string"), type_name);
+ ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull());
+}
+
+// AAPT(2) generates resource tables with chunks in a certain order. The rule is that
+// a RES_TABLE_TYPE_TYPE with id `i` must always be preceded by a RES_TABLE_TYPE_SPEC_TYPE with
+// id `i`. The RES_TABLE_TYPE_SPEC_TYPE does not need to be directly preceding, however.
+//
+// AAPT(2) generates something like:
+// RES_TABLE_TYPE_SPEC_TYPE id=1
+// RES_TABLE_TYPE_TYPE id=1
+// RES_TABLE_TYPE_SPEC_TYPE id=2
+// RES_TABLE_TYPE_TYPE id=2
+//
+// But the following is valid too:
+// RES_TABLE_TYPE_SPEC_TYPE id=1
+// RES_TABLE_TYPE_SPEC_TYPE id=2
+// RES_TABLE_TYPE_TYPE id=1
+// RES_TABLE_TYPE_TYPE id=2
+//
+TEST(LoadedArscTest, LoadOutOfOrderTypeSpecs) {
+ std::string contents;
+ ASSERT_TRUE(
+ ReadFileFromZipToString(GetTestDataPath() + "/out_of_order_types/out_of_order_types.apk",
+ "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
+ const auto& package = loaded_arsc->GetPackages()[0];
+ ASSERT_THAT(package, NotNull());
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+ ASSERT_THAT(type_spec->types[0], NotNull());
+
+ type_spec = package->GetTypeSpecByTypeIndex(1);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+ ASSERT_THAT(type_spec->types[0], NotNull());
}
class MockLoadedIdmap : public LoadedIdmap {
@@ -199,23 +244,33 @@
};
TEST(LoadedArscTest, LoadOverlay) {
- std::string contents, overlay_contents;
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+ std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
- &overlay_contents));
+ &contents));
MockLoadedIdmap loaded_idmap;
std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
- ASSERT_NE(nullptr, loaded_arsc);
+ LoadedArsc::Load(StringPiece(contents), &loaded_idmap);
+ ASSERT_THAT(loaded_arsc, NotNull());
- ResTable_config desired_config;
- memset(&desired_config, 0, sizeof(desired_config));
+ const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u);
+ ASSERT_THAT(package, NotNull());
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+ ASSERT_THAT(type_spec->types[0], NotNull());
+
+ // The entry being overlaid doesn't exist at the original entry index.
+ ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull());
+
+ // Since this is an overlay, the actual entry ID must be mapped.
+ ASSERT_THAT(type_spec->idmap_entries, NotNull());
+ uint16_t target_entry_id = 0u;
+ ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id));
+ ASSERT_THAT(target_entry_id, Eq(0x0u));
+ ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull());
}
// structs with size fields (like Res_value, ResTable_entry) should be
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index 43a9955..df0c642 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -20,6 +20,7 @@
#include <string>
#include "androidfw/ResourceTypes.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "CommonHelpers.h"
diff --git a/libs/androidfw/tests/data/app/app.apk b/libs/androidfw/tests/data/app/app.apk
index ccb0824..c8ad86d 100644
--- a/libs/androidfw/tests/data/app/app.apk
+++ b/libs/androidfw/tests/data/app/app.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/app/assets/app_file.txt b/libs/androidfw/tests/data/app/assets/app_file.txt
new file mode 100644
index 0000000..b214e06
--- /dev/null
+++ b/libs/androidfw/tests/data/app/assets/app_file.txt
@@ -0,0 +1 @@
+app file
diff --git a/libs/androidfw/tests/data/app/assets/file.txt b/libs/androidfw/tests/data/app/assets/file.txt
new file mode 100644
index 0000000..0811542
--- /dev/null
+++ b/libs/androidfw/tests/data/app/assets/file.txt
@@ -0,0 +1 @@
+app override file
diff --git a/libs/androidfw/tests/data/app/build b/libs/androidfw/tests/data/app/build
index d418158..09af842 100755
--- a/libs/androidfw/tests/data/app/build
+++ b/libs/androidfw/tests/data/app/build
@@ -17,4 +17,11 @@
set -e
-aapt package -I ../system/system.apk -M AndroidManifest.xml -S res -F app.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link \
+ --manifest AndroidManifest.xml \
+ -I ../system/system.apk \
+ -A assets \
+ -o app.apk \
+ compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index 94a2a14..b7e814f 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -34,6 +34,7 @@
struct layout {
enum : uint32_t {
main = 0x7f020000,
+ layoutt = 0x7f020001,
};
};
@@ -55,6 +56,7 @@
number2 = 0x7f040001,
ref1 = 0x7f040002,
ref2 = 0x7f040003,
+ deep_ref = 0x7f040004,
// From feature
number3 = 0x80030000,
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 18ef75e..1733b6a 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml
new file mode 100644
index 0000000..045ede4
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/ok"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginStart="2dip"
+ android:layout_marginEnd="2dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:text="@android:string/ok" />
\ No newline at end of file
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 6c47459..b343562 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -22,6 +22,7 @@
<attr name="attr2" format="reference|integer" />
<public type="layout" name="main" id="0x7f020000" />
+ <public type="layout" name="layout" id="0x7f020001" />
<public type="string" name="test1" id="0x7f030000" />
<string name="test1">test1</string>
@@ -43,6 +44,18 @@
<public type="integer" name="ref2" id="0x7f040003" />
<integer name="ref2">12000</integer>
+ <public type="integer" name="deep_ref" id="0x7f040004" />
+ <integer name="deep_ref">@integer/deep_ref_1</integer>
+ <integer name="deep_ref_1">@integer/deep_ref_2</integer>
+ <integer name="deep_ref_2">@integer/deep_ref_3</integer>
+ <integer name="deep_ref_3">@integer/deep_ref_4</integer>
+ <integer name="deep_ref_4">@integer/deep_ref_5</integer>
+ <integer name="deep_ref_5">@integer/deep_ref_6</integer>
+ <integer name="deep_ref_6">@integer/deep_ref_7</integer>
+ <integer name="deep_ref_7">@integer/deep_ref_8</integer>
+ <integer name="deep_ref_8">@integer/deep_ref_9</integer>
+ <integer name="deep_ref_9">100</integer>
+
<public type="style" name="Theme1" id="0x7f050000" />
<style name="Theme1">
<item name="com.android.basic:attr1">100</item>
diff --git a/libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml b/libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml
new file mode 100644
index 0000000..34016db
--- /dev/null
+++ b/libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app" />
diff --git a/libs/androidfw/tests/data/out_of_order_types/build b/libs/androidfw/tests/data/out_of_order_types/build
new file mode 100755
index 0000000..8496f81
--- /dev/null
+++ b/libs/androidfw/tests/data/out_of_order_types/build
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -e
+
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -o out_of_order_types.apk compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt b/libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt
new file mode 100644
index 0000000..eca8f47
--- /dev/null
+++ b/libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt
@@ -0,0 +1,43 @@
+00000000: 0200 0c00 ac02 0000 0100 0000 0100 1c00 ................
+00000010: 1c00 0000 0000 0000 0000 0000 0001 0000 ................
+00000020: 1c00 0000 0000 0000 0002 2001 8402 0000 .......... .....
+00000030: 7f00 0000 6300 6f00 6d00 2e00 6100 6e00 ....c.o.m...a.n.
+00000040: 6400 7200 6f00 6900 6400 2e00 6100 7000 d.r.o.i.d...a.p.
+00000050: 7000 0000 0000 0000 0000 0000 0000 0000 p...............
+00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000130: 0000 0000 2001 0000 0000 0000 6401 0000 .... .......d...
+00000140: 0000 0000 0000 0000 0100 1c00 4400 0000 ............D...
+00000150: 0200 0000 0000 0000 0000 0000 2400 0000 ............$...
+00000160: 0000 0000 0000 0000 0c00 0000 0400 6200 ..............b.
+00000170: 6f00 6f00 6c00 0000 0700 6900 6e00 7400 o.o.l.....i.n.t.
+00000180: 6500 6700 6500 7200 0000 0000 0100 1c00 e.g.e.r.........
+00000190: 2800 0000 0100 0000 0000 0000 0001 0000 (...............
+000001a0: 2000 0000 0000 0000 0000 0000 0404 7465 .............te
+000001b0: 7374 0000 0202 1000 1400 0000 0100 0000 st..............
+000001c0: 0100 0000 0000 0000 0202 1000 1400 0000
+000001d0: 0200 0000 0100 0000 0000 0000 0102 5400
+000001e0: 6800 0000 0100 0000 0100 0000 5800 0000
+000001f0: 4000 0000 0000 0000 0000 0000 0000 0000
+00000200: 0000 0000 0000 0000 0000 0000 0000 0000
+00000210: 0000 0000 0000 0000 0000 0000 0000 0000
+00000220: 0000 0000 0000 0000 0000 0000 0000 0000
+00000230: 0000 0000 0800 0000 0000 0000 0800 0012
+00000240: ffff ffff 0102 5400 6800 0000 0200 0000 ......T.h.......
+00000250: 0100 0000 5800 0000 4000 0000 0000 0000 ....X...@.......
+00000260: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000270: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000280: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+00000290: 0000 0000 0000 0000 0000 0000 0800 0000 ................
+000002a0: 0000 0000 0800 0010 0100 0000 ............
diff --git a/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk b/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk
new file mode 100644
index 0000000..75146e0
--- /dev/null
+++ b/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/out_of_order_types/res/values/values.xml b/libs/androidfw/tests/data/out_of_order_types/res/values/values.xml
new file mode 100644
index 0000000..7c54fba
--- /dev/null
+++ b/libs/androidfw/tests/data/out_of_order_types/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <bool name="test">true</bool>
+ <integer name="test">1</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/system/assets/file.txt b/libs/androidfw/tests/data/system/assets/file.txt
new file mode 100644
index 0000000..f73f309
--- /dev/null
+++ b/libs/androidfw/tests/data/system/assets/file.txt
@@ -0,0 +1 @@
+file
diff --git a/libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt b/libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt
new file mode 100644
index 0000000..3f74eb6
--- /dev/null
+++ b/libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt
@@ -0,0 +1 @@
+subdir file
diff --git a/libs/androidfw/tests/data/system/build b/libs/androidfw/tests/data/system/build
index bfbdf4c..b65145a 100755
--- a/libs/androidfw/tests/data/system/build
+++ b/libs/androidfw/tests/data/system/build
@@ -17,4 +17,6 @@
set -e
-aapt package -x -M AndroidManifest.xml -S res -F system.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -A assets -o system.apk compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/system/res/values-sv/values.xml b/libs/androidfw/tests/data/system/res/values-sv/values.xml
index b97bdb6..5f60d21 100644
--- a/libs/androidfw/tests/data/system/res/values-sv/values.xml
+++ b/libs/androidfw/tests/data/system/res/values-sv/values.xml
@@ -15,6 +15,5 @@
-->
<resources>
- <public type="integer" name="number" id="0x01030000" />
<integer name="number">1</integer>
</resources>
diff --git a/libs/androidfw/tests/data/system/res/values/themes.xml b/libs/androidfw/tests/data/system/res/values/themes.xml
index 35d43c7..7893c94 100644
--- a/libs/androidfw/tests/data/system/res/values/themes.xml
+++ b/libs/androidfw/tests/data/system/res/values/themes.xml
@@ -18,6 +18,7 @@
<public name="background" type="attr" id="0x01010000"/>
<public name="foreground" type="attr" id="0x01010001"/>
<public name="Theme.One" type="style" id="0x01020000"/>
+ <public type="integer" name="number" id="0x01030000" />
<attr name="background" format="color|reference"/>
<attr name="foreground" format="color|reference"/>
diff --git a/libs/androidfw/tests/data/system/system.apk b/libs/androidfw/tests/data/system/system.apk
index 1299016..9045d6c 100644
--- a/libs/androidfw/tests/data/system/system.apk
+++ b/libs/androidfw/tests/data/system/system.apk
Binary files differ
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index b2edd33..2b0b22d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -187,11 +187,6 @@
static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) {
SkCanvas::SaveLayerFlags layerFlags = 0;
- // We intentionally ignore the SaveFlags::HasAlphaLayer and
- // SkCanvas::kIsOpaque_SaveLayerFlag flags because HWUI ignores it
- // and our Android client may use it incorrectly.
- // In Skia, this flag is purely for performance optimization.
-
if (!(flags & SaveFlags::ClipToLayer)) {
layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
}
@@ -747,6 +742,12 @@
SkPaint paintCopy(paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
+ // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
+ // older.
+ if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0
+ && paintCopy.getStyle() == SkPaint::kStroke_Style) {
+ paintCopy.setStyle(SkPaint::kFill_Style);
+ }
SkRect bounds =
SkRect::MakeLTRB(boundsLeft + x, boundsTop + y, boundsRight + x, boundsBottom + y);
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 06e2d6c..fc009d8 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -204,10 +204,6 @@
saveFlags |= SaveFlags::ClipToLayer;
}
- if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) {
- saveFlags |= SaveFlags::HasAlphaLayer;
- }
-
return saveFlags;
}
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index f118e8d..f662406 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -557,13 +557,12 @@
mAtlasKey = INVALID_ATLAS_KEY;
}
-void Tree::draw(SkCanvas* canvas) {
+void Tree::draw(SkCanvas* canvas, const SkRect& bounds) {
SkRect src;
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
if (vdSurface) {
canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src,
- mutateProperties()->getBounds(), getPaint(),
- SkCanvas::kFast_SrcRectConstraint);
+ bounds, getPaint(), SkCanvas::kFast_SrcRectConstraint);
} else {
// Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure.
// We render the VD into a temporary standalone buffer and mark the frame as dirty. Next
@@ -575,8 +574,7 @@
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight),
- mutateProperties()->getBounds(), getPaint(),
- SkCanvas::kFast_SrcRectConstraint);
+ bounds, getPaint(), SkCanvas::kFast_SrcRectConstraint);
mCache.clear();
markDirty();
}
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index d9cf8ab..da52a95 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -644,7 +644,7 @@
* Draws VD cache into a canvas. This should always be called from RT and it works with Skia
* pipelines only.
*/
- void draw(SkCanvas* canvas);
+ void draw(SkCanvas* canvas, const SkRect& bounds);
/**
* Draws VD into a GPU backed surface.
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index a75276f..4f06656 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -66,7 +66,7 @@
Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);
int rowBytesAsPixels() const {
- return rowBytes() >> SkColorTypeShiftPerPixel(mInfo.colorType());
+ return rowBytes() >> mInfo.shiftPerPixel();
}
void reconfigure(const SkImageInfo& info, size_t rowBytes);
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 284fd83..ad4c8be 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -225,4 +225,10 @@
MinikinUtils::forFontRun(layout, &paintCopy, f);
}
+int Canvas::sApiLevel = 1;
+
+void Canvas::setCompatibilityVersion(int apiLevel) {
+ sApiLevel = apiLevel;
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 3ddf1c4..fabb8d2 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -116,6 +116,14 @@
static Canvas* create_canvas(SkCanvas* skiaCanvas);
/**
+ * Sets the target SDK version used to build the app.
+ *
+ * @param apiLevel API level
+ *
+ */
+ static void setCompatibilityVersion(int apiLevel);
+
+ /**
* Provides a Skia SkCanvas interface that acts as a proxy to this Canvas.
* It is useful for testing and clients (e.g. Picture/Movie) that expect to
* draw their contents into an SkCanvas.
@@ -282,6 +290,8 @@
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const SkPaint& paint, const SkPath& path, size_t start,
size_t end) = 0;
+ static int sApiLevel;
+
friend class DrawTextFunctor;
friend class DrawTextOnPathFunctor;
friend class uirenderer::SkiaCanvasProxy;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 091b526..dca9ef5 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -132,8 +132,8 @@
bool italicFromFont;
const minikin::FontStyle defaultStyle;
- const minikin::MinikinFont* mf =
- families.empty() ? nullptr : families[0]->getClosestMatch(defaultStyle).font;
+ const minikin::MinikinFont* mf = families.empty() ? nullptr
+ : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
if (mf != nullptr) {
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
const SkFontStyle& style = skTypeface->fontStyle();
@@ -183,7 +183,7 @@
std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
std::vector<minikin::Font> fonts;
- fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle()));
+ fonts.push_back(minikin::Font::Builder(font).build());
std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
std::make_shared<minikin::FontFamily>(std::move(fonts)));
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index eabe2e8..25c76eb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -124,14 +124,19 @@
class VectorDrawable : public SkDrawable {
public:
- VectorDrawable(VectorDrawableRoot* tree) : mRoot(tree) {}
+ VectorDrawable(VectorDrawableRoot* tree)
+ : mRoot(tree)
+ , mBounds(tree->stagingProperties()->getBounds()) {}
protected:
- virtual SkRect onGetBounds() override { return SkRect::MakeLargest(); }
- virtual void onDraw(SkCanvas* canvas) override { mRoot->draw(canvas); }
+ virtual SkRect onGetBounds() override { return mBounds; }
+ virtual void onDraw(SkCanvas* canvas) override {
+ mRoot->draw(canvas, mBounds);
+ }
private:
sp<VectorDrawableRoot> mRoot;
+ SkRect mBounds;
};
void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 2953ea8..15c0ab1 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -471,6 +471,7 @@
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
void onCopyOnWrite(ContentChangeMode) override {}
int* mDrawCounter;
+ void onWritePixels(const SkPixmap&, int x, int y) {}
};
auto receiverBackground = TestUtils::createSkiaNode(
@@ -1143,4 +1144,47 @@
RenderNodeDrawable drawable(parent.get(), &canvas, false);
canvas.drawDrawable(&drawable);
EXPECT_EQ(6, canvas.getIndex());
-}
\ No newline at end of file
+}
+
+// Draw a vector drawable twice but with different bounds and verify correct bounds are used.
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
+ static const int CANVAS_WIDTH = 100;
+ static const int CANVAS_HEIGHT = 200;
+ class VectorDrawableTestCanvas : public TestCanvasBase {
+ public:
+ VectorDrawableTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+ void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
+ const SkPaint* paint, SrcRectConstraint constraint) override {
+ const int index = mDrawCounter++;
+ switch (index) {
+ case 0:
+ EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT));
+ break;
+ case 1:
+ EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH/2, CANVAS_HEIGHT));
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ }
+ };
+
+ VectorDrawable::Group* group = new VectorDrawable::Group();
+ sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group));
+ vectorDrawable->mutateStagingProperties()->setScaledSize(CANVAS_WIDTH/10, CANVAS_HEIGHT/10);
+
+ auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [&](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH,
+ CANVAS_HEIGHT));
+ canvas.drawVectorDrawable(vectorDrawable.get());
+ vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH/2,
+ CANVAS_HEIGHT));
+ canvas.drawVectorDrawable(vectorDrawable.get());
+ });
+
+ VectorDrawableTestCanvas canvas;
+ RenderNodeDrawable drawable(node.get(), &canvas, true);
+ canvas.drawDrawable(&drawable);
+ EXPECT_EQ(2, canvas.mDrawCounter);
+}
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 8fdb0e3..42a92fc 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -250,6 +250,7 @@
sk_sp<SkImage> onNewImageSnapshot() override { return nullptr; }
T* canvas() { return static_cast<T*>(getCanvas()); }
void onCopyOnWrite(ContentChangeMode) override {}
+ void onWritePixels(const SkPixmap&, int x, int y) override {}
};
}
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 2232c25..e424a26 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -57,7 +57,7 @@
std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
std::vector<minikin::Font> fonts;
- fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle()));
+ fonts.push_back(minikin::Font::Builder(font).build());
return std::make_shared<minikin::FontFamily>(std::move(fonts));
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index c33dce1..d194796 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -236,6 +236,62 @@
*/
public static final String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown";
+ /**
+ * Broadcast intent action for Settings app to inject a footer at the bottom of location
+ * settings.
+ *
+ * <p>This broadcast is used for two things:
+ * <ol>
+ * <li>For receivers to inject a footer with provided text. This is for use only by apps
+ * that are included in the system image. </li>
+ * <li>For receivers to know their footer is injected under location settings.</li>
+ * </ol>
+ *
+ * <p>To inject a footer to location settings, you must declare a broadcast receiver of
+ * {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} in the manifest as so:
+ * <pre>
+ * <receiver android:name="com.example.android.footer.MyFooterInjector">
+ * <intent-filter>
+ * <action android:name="com.android.settings.location.INJECT_FOOTER" />
+ * </intent-filter>
+ * <meta-data
+ * android:name="com.android.settings.location.FOOTER_STRING"
+ * android:resource="@string/my_injected_footer_string" />
+ * </receiver>
+ * </pre>
+ *
+ * <p>On entering location settings, Settings app will send a
+ * {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast to receivers whose footer is successfully
+ * injected. On leaving location settings, the footer becomes not visible to users. Settings app
+ * will send a {@link #SETTINGS_FOOTER_REMOVED_ACTION} broadcast to those receivers.
+ *
+ * @hide
+ */
+ public static final String SETTINGS_FOOTER_DISPLAYED_ACTION =
+ "com.android.settings.location.DISPLAYED_FOOTER";
+
+ /**
+ * Broadcast intent action when location settings footer is not visible to users.
+ *
+ * <p>See {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} for more detail on how to use.
+ *
+ * @hide
+ */
+ public static final String SETTINGS_FOOTER_REMOVED_ACTION =
+ "com.android.settings.location.REMOVED_FOOTER";
+
+ /**
+ * Metadata name for {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast
+ * receivers to specify a string resource id as location settings footer text. This is for use
+ * only by apps that are included in the system image.
+ *
+ * <p>See {@link #SETTINGS_FOOTER_DISPLAYED_ACTION} for more detail on how to use.
+ *
+ * @hide
+ */
+ public static final String METADATA_SETTINGS_FOOTER_STRING =
+ "com.android.settings.location.FOOTER_STRING";
+
// Map from LocationListeners to their associated ListenerTransport objects
private HashMap<LocationListener,ListenerTransport> mListeners =
new HashMap<LocationListener,ListenerTransport>();
@@ -1215,6 +1271,9 @@
final String allowedProviders = Settings.Secure.getStringForUser(
mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
userHandle.getIdentifier());
+ if (allowedProviders == null) {
+ return false;
+ }
final List<String> providerList = Arrays.asList(allowedProviders.split(","));
for(String provider : getAllProviders()) {
if (provider.equals(PASSIVE_PROVIDER)) {
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 5d0c8e2..5467a69 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -38,6 +38,10 @@
private int mLossReceived;
private int mFlags;
+ // generation count for the validity of a request/response async exchange between
+ // external focus policy and MediaFocusControl
+ private long mGenCount = -1;
+
/**
* Class constructor
@@ -61,6 +65,16 @@
mSdkTarget = sdk;
}
+ /** @hide */
+ public void setGen(long g) {
+ mGenCount = g;
+ }
+
+ /** @hide */
+ public long getGen() {
+ return mGenCount;
+ }
+
/**
* The audio attributes for the audio focus request.
@@ -128,6 +142,7 @@
dest.writeInt(mLossReceived);
dest.writeInt(mFlags);
dest.writeInt(mSdkTarget);
+ dest.writeLong(mGenCount);
}
@Override
@@ -168,6 +183,8 @@
if (mSdkTarget != other.mSdkTarget) {
return false;
}
+ // mGenCount is not used to verify equality between two focus holds as multiple requests
+ // (hence of different generations) could correspond to the same hold
return true;
}
@@ -175,7 +192,7 @@
= new Parcelable.Creator<AudioFocusInfo>() {
public AudioFocusInfo createFromParcel(Parcel in) {
- return new AudioFocusInfo(
+ final AudioFocusInfo afi = new AudioFocusInfo(
AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
in.readInt(), // int clientUid
in.readString(), //String clientId
@@ -185,6 +202,8 @@
in.readInt(), //int flags
in.readInt() //int sdkTarget
);
+ afi.setGen(in.readLong());
+ return afi;
}
public AudioFocusInfo[] newArray(int size) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bf51d97..0be54ec 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.Intent;
import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
@@ -54,10 +55,13 @@
import android.util.Slog;
import android.view.KeyEvent;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -2338,6 +2342,20 @@
}
}
}
+
+ @Override
+ public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) {
+ synchronized (mFocusRequestsLock) {
+ // TODO use generation counter as the key instead
+ final BlockingFocusResultReceiver focusReceiver =
+ mFocusRequestsAwaitingResult.remove(clientId);
+ if (focusReceiver != null) {
+ focusReceiver.notifyResult(requestResult);
+ } else {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver");
+ }
+ }
+ }
};
private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
@@ -2390,6 +2408,40 @@
*/
public static final int AUDIOFOCUS_REQUEST_DELAYED = 2;
+ /** @hide */
+ @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = {
+ AUDIOFOCUS_REQUEST_FAILED,
+ AUDIOFOCUS_REQUEST_GRANTED,
+ AUDIOFOCUS_REQUEST_DELAYED }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRequestResult {}
+
+ /**
+ * @hide
+ * code returned when a synchronous focus request on the client-side is to be blocked
+ * until the external audio focus policy decides on the response for the client
+ */
+ public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100;
+
+ /**
+ * Timeout duration in ms when waiting on an external focus policy for the result for a
+ * focus request
+ */
+ private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200;
+
+ private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id";
+
+ private final Object mFocusRequestsLock = new Object();
+ /**
+ * Map of all receivers of focus request results, one per unresolved focus request.
+ * Receivers are added before sending the request to the external focus policy,
+ * and are removed either after receiving the result, or after the timeout.
+ * This variable is lazily initialized.
+ */
+ @GuardedBy("mFocusRequestsLock")
+ private HashMap<String, BlockingFocusResultReceiver> mFocusRequestsAwaitingResult;
+
/**
* Request audio focus.
@@ -2656,18 +2708,100 @@
// some tests don't have a Context
sdk = Build.VERSION.SDK_INT;
}
- try {
- status = service.requestAudioFocus(afr.getAudioAttributes(),
- afr.getFocusGain(), mICallBack,
- mAudioFocusDispatcher,
- getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()),
- getContext().getOpPackageName() /* package name */, afr.getFlags(),
- ap != null ? ap.cb() : null,
- sdk);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+
+ final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
+ final BlockingFocusResultReceiver focusReceiver;
+ synchronized (mFocusRequestsLock) {
+ try {
+ // TODO status contains result and generation counter for ext policy
+ status = service.requestAudioFocus(afr.getAudioAttributes(),
+ afr.getFocusGain(), mICallBack,
+ mAudioFocusDispatcher,
+ clientId,
+ getContext().getOpPackageName() /* package name */, afr.getFlags(),
+ ap != null ? ap.cb() : null,
+ sdk);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+ // default path with no external focus policy
+ return status;
+ }
+ if (mFocusRequestsAwaitingResult == null) {
+ mFocusRequestsAwaitingResult =
+ new HashMap<String, BlockingFocusResultReceiver>(1);
+ }
+ focusReceiver = new BlockingFocusResultReceiver(clientId);
+ mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
}
- return status;
+ focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
+ if (DEBUG && !focusReceiver.receivedResult()) {
+ Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
+ }
+ synchronized (mFocusRequestsLock) {
+ mFocusRequestsAwaitingResult.remove(clientId);
+ }
+ return focusReceiver.requestResult();
+ }
+
+ // helper class that abstracts out the handling of spurious wakeups in Object.wait()
+ private static final class SafeWaitObject {
+ private boolean mQuit = false;
+
+ public void safeNotify() {
+ synchronized (this) {
+ mQuit = true;
+ this.notify();
+ }
+ }
+
+ public void safeWait(long millis) throws InterruptedException {
+ final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
+ synchronized (this) {
+ while (!mQuit) {
+ final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
+ if (timeToWait < 0) { break; }
+ this.wait(timeToWait);
+ }
+ }
+ }
+ }
+
+ private static final class BlockingFocusResultReceiver {
+ private final SafeWaitObject mLock = new SafeWaitObject();
+ @GuardedBy("mLock")
+ private boolean mResultReceived = false;
+ // request denied by default (e.g. timeout)
+ private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ private final String mFocusClientId;
+
+ BlockingFocusResultReceiver(String clientId) {
+ mFocusClientId = clientId;
+ }
+
+ boolean receivedResult() { return mResultReceived; }
+ int requestResult() { return mFocusRequestResult; }
+
+ void notifyResult(int requestResult) {
+ synchronized (mLock) {
+ mResultReceived = true;
+ mFocusRequestResult = requestResult;
+ mLock.safeNotify();
+ }
+ }
+
+ public void waitForResult(long timeOutMs) {
+ synchronized (mLock) {
+ if (mResultReceived) {
+ // the result was received before waiting
+ return;
+ }
+ try {
+ mLock.safeWait(timeOutMs);
+ } catch (InterruptedException e) { }
+ }
+ }
}
/**
@@ -2714,6 +2848,32 @@
/**
* @hide
+ * Set the result to the audio focus request received through
+ * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}.
+ * @param afi the information about the focus requester
+ * @param requestResult the result to the focus request to be passed to the requester
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setFocusRequestResult(@NonNull AudioFocusInfo afi,
+ @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (ap == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy");
+ }
+ final IAudioService service = getService();
+ try {
+ service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Notifies an application with a focus listener of gain or loss of audio focus.
* This method can only be used by owners of an {@link AudioPolicy} configured with
* {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index d0963cb..3847530 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1416,6 +1416,7 @@
/*
* Call BEFORE adding a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testEnableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback();
@@ -1425,6 +1426,7 @@
/*
* Call AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testDisableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_disableDeviceCallback();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 8e822a5..2d5fad5 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2821,6 +2821,7 @@
/*
* Call BEFORE adding a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testEnableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback();
@@ -2830,6 +2831,7 @@
/*
* Call AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testDisableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_disableDeviceCallback();
diff --git a/media/java/android/media/IAudioFocusDispatcher.aidl b/media/java/android/media/IAudioFocusDispatcher.aidl
index 09575f7..3b33c5b 100644
--- a/media/java/android/media/IAudioFocusDispatcher.aidl
+++ b/media/java/android/media/IAudioFocusDispatcher.aidl
@@ -25,4 +25,6 @@
void dispatchAudioFocusChange(int focusChange, String clientId);
+ void dispatchFocusResultFromExtPolicy(int requestResult, String clientId);
+
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 88d0a60..4c37014 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -16,9 +16,7 @@
package android.media;
-import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
-import android.content.ComponentName;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioPlaybackConfiguration;
@@ -31,25 +29,33 @@
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.PlayerBase;
-import android.media.Rating;
import android.media.VolumePolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.IAudioPolicyCallback;
-import android.net.Uri;
-import android.view.KeyEvent;
/**
* {@hide}
*/
interface IAudioService {
+ // C++ and Java methods below.
- // WARNING: When methods are inserted or deleted, the transaction IDs in
+ // WARNING: When methods are inserted or deleted in this section, the transaction IDs in
// frameworks/native/include/audiomanager/IAudioManager.h must be updated to match the order
// in this file.
//
// When a method's argument list is changed, BpAudioManager's corresponding serialization code
// (if any) in frameworks/native/services/audiomanager/IAudioManager.cpp must be updated.
+ int trackPlayer(in PlayerBase.PlayerIdCard pic);
+
+ oneway void playerAttributes(in int piid, in AudioAttributes attr);
+
+ oneway void playerEvent(in int piid, in int event);
+
+ oneway void releasePlayer(in int piid);
+
+ // Java-only methods below.
+
oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller);
@@ -187,14 +193,6 @@
List<AudioPlaybackConfiguration> getActivePlaybackConfigurations();
- int trackPlayer(in PlayerBase.PlayerIdCard pic);
-
- oneway void playerAttributes(in int piid, in AudioAttributes attr);
-
- oneway void playerEvent(in int piid, in int event);
-
- oneway void releasePlayer(in int piid);
-
void disableRingtoneSync(in int userId);
int getFocusRampTimeMs(in int focusGain, in AudioAttributes attr);
@@ -207,5 +205,9 @@
int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device,
int state, int profile, boolean suppressNoisyIntent);
- // WARNING: read warning at top of file, it is recommended to add new methods at the end
+ oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult,
+ in IAudioPolicyCallback pcb);
+
+ // WARNING: read warning at top of file, new methods that need to be used by native
+ // code via IAudioManager.h need to be added to the top section.
}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index e9ffe60..bd6c7e6 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -30,7 +30,7 @@
import android.media.session.MediaSessionManager;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
-import android.media.update.PlaybackInfoProvider;
+import android.media.update.MediaController2Provider.PlaybackInfoProvider;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
diff --git a/media/java/android/media/MediaDataSource.java b/media/java/android/media/MediaDataSource.java
index 948da0b..4ba2120 100644
--- a/media/java/android/media/MediaDataSource.java
+++ b/media/java/android/media/MediaDataSource.java
@@ -34,8 +34,8 @@
/**
* Called to request data from the given position.
*
- * Implementations should should write up to {@code size} bytes into
- * {@code buffer}, and return the number of bytes written.
+ * Implementations should fill {@code buffer} with up to {@code size}
+ * bytes of data, and return the number of valid bytes in the buffer.
*
* Return {@code 0} if size is zero (thus no bytes are read).
*
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1bc3dfa..fe5e822 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1484,6 +1484,7 @@
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void enableNativeRoutingCallbacksLocked(boolean enabled) {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index e3d5ac0..d4e9aac 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -1417,6 +1417,7 @@
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void enableNativeRoutingCallbacksLocked(boolean enabled) {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 62240ce..823410f 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1353,6 +1353,7 @@
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void enableNativeRoutingCallbacksLocked(boolean enabled) {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java
index bbcce82..bd6667f 100644
--- a/media/java/android/media/VolumePolicy.java
+++ b/media/java/android/media/VolumePolicy.java
@@ -23,7 +23,7 @@
/** @hide */
public final class VolumePolicy implements Parcelable {
- public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, true, 400);
+ public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, false, 400);
/**
* Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 4de731a..2190635 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -463,9 +463,9 @@
* Only ever called if the {@link AudioPolicy} was built with
* {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
* @param afi information about the focus request and the requester
- * @param requestResult the result that was returned synchronously by the framework to the
- * application, {@link #AUDIOFOCUS_REQUEST_FAILED},or
- * {@link #AUDIOFOCUS_REQUEST_DELAYED}.
+ * @param requestResult deprecated after the addition of
+ * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)}
+ * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}.
*/
public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
/**
@@ -534,7 +534,7 @@
sendMsg(MSG_FOCUS_REQUEST, afi, requestResult);
if (DEBUG) {
Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client="
- + afi.getClientId() + "reqRes=" + requestResult);
+ + afi.getClientId() + " gen=" + afi.getGen());
}
}
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 71bc64a..c492d307 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -18,6 +18,7 @@
import android.annotation.SystemApi;
import android.app.PendingIntent;
+import android.media.AudioAttributes;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
import android.media.MediaSession2.Command;
@@ -65,4 +66,12 @@
PlaylistParams getPlaylistParams_impl();
void setPlaylistParams_impl(PlaylistParams params);
PlaybackState2 getPlaybackState_impl();
+
+ interface PlaybackInfoProvider {
+ int getPlaybackType_impl();
+ AudioAttributes getAudioAttributes_impl();
+ int getControlType_impl();
+ int getMaxVolume_impl();
+ int getCurrentVolume_impl();
+ }
}
diff --git a/media/java/android/media/update/PlaybackInfoProvider.java b/media/java/android/media/update/PlaybackInfoProvider.java
deleted file mode 100644
index 36eb58a..0000000
--- a/media/java/android/media/update/PlaybackInfoProvider.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.update;
-
-import android.media.AudioAttributes;
-
-/**
- * @hide
- */
-// TODO(jaewan): @SystemApi
-public interface PlaybackInfoProvider {
- int getPlaybackType_impl();
- AudioAttributes getAudioAttributes_impl();
- int getControlType_impl();
- int getMaxVolume_impl();
- int getCurrentVolume_impl();
-}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 7e5f581..fe2f64f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -127,11 +127,11 @@
"liblzma",
"libmedia",
"libmedia_helper",
- "libmedia_player2",
"libmedia_player2_util",
"libmediadrm",
"libmediaextractor",
"libmediametrics",
+ "libmediaplayer2",
"libmediautils",
"libnativehelper",
"libnetd_client",
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 27eaed0..0eb98f3 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -21,15 +21,14 @@
#include <sys/stat.h>
-#include <media/mediaplayer2.h>
#include <media/AudioResamplerPublic.h>
#include <media/DataSourceDesc.h>
#include <media/MediaHTTPService.h>
-#include <media/MediaPlayer2Interface.h>
#include <media/MediaAnalyticsItem.h>
#include <media/NdkWrapper.h>
#include <media/stagefright/Utils.h>
#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
+#include <mediaplayer2/mediaplayer2.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 98e9a42..e70d5ea 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -18,9 +18,11 @@
#include <utils/Log.h>
#include <android/asset_manager_jni.h>
+#include <android_runtime/android_util_AssetManager.h>
#include <androidfw/Asset.h>
#include <androidfw/AssetDir.h>
#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include <utils/threads.h>
#include "jni.h"
@@ -35,21 +37,20 @@
// -----
struct AAssetDir {
- AssetDir* mAssetDir;
+ std::unique_ptr<AssetDir> mAssetDir;
size_t mCurFileIndex;
String8 mCachedFileName;
- explicit AAssetDir(AssetDir* dir) : mAssetDir(dir), mCurFileIndex(0) { }
- ~AAssetDir() { delete mAssetDir; }
+ explicit AAssetDir(std::unique_ptr<AssetDir> dir) :
+ mAssetDir(std::move(dir)), mCurFileIndex(0) { }
};
// -----
struct AAsset {
- Asset* mAsset;
+ std::unique_ptr<Asset> mAsset;
- explicit AAsset(Asset* asset) : mAsset(asset) { }
- ~AAsset() { delete mAsset; }
+ explicit AAsset(std::unique_ptr<Asset> asset) : mAsset(std::move(asset)) { }
};
// -------------------- Public native C API --------------------
@@ -104,19 +105,18 @@
return NULL;
}
- AssetManager* mgr = static_cast<AssetManager*>(amgr);
- Asset* asset = mgr->open(filename, amMode);
- if (asset == NULL) {
- return NULL;
+ ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr));
+ std::unique_ptr<Asset> asset = locked_mgr->Open(filename, amMode);
+ if (asset == nullptr) {
+ return nullptr;
}
-
- return new AAsset(asset);
+ return new AAsset(std::move(asset));
}
AAssetDir* AAssetManager_openDir(AAssetManager* amgr, const char* dirName)
{
- AssetManager* mgr = static_cast<AssetManager*>(amgr);
- return new AAssetDir(mgr->openDir(dirName));
+ ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr));
+ return new AAssetDir(locked_mgr->OpenDir(dirName));
}
/**
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index 77237ae..87fe9ed 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -17,9 +17,10 @@
#define LOG_TAG "Configuration"
#include <utils/Log.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include <android_runtime/android_content_res_Configuration.h>
+#include <android_runtime/android_util_AssetManager.h>
using namespace android;
@@ -34,7 +35,11 @@
}
void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
- ((AssetManager*)am)->getConfiguration(out);
+ ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
+ ResTable_config config = locked_mgr->GetConfiguration();
+
+ // AConfiguration is not a virtual subclass, so we can memcpy.
+ memcpy(out, &config, sizeof(config));
}
void AConfiguration_copy(AConfiguration* dest, AConfiguration* src) {
diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml
index 8d79f62..c0a59b3 100644
--- a/packages/MtpDocumentsProvider/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="com.android.mtp"
android:sharedUserId="android.media">
<uses-feature android:name="android.hardware.usb.host" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MANAGE_USB" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
index 72be35b..15dd64b 100644
--- a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
@@ -20,6 +20,8 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="PrintSpoolerOutOfProcessTests" />
+ <option name="config-descriptor:metadata" key="component" value="print" />
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.printspooler.outofprocess.tests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index 741db34..3c2ca2d 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -16,11 +16,11 @@
ifeq ($(LOCAL_USE_AAPT2),true)
LOCAL_STATIC_JAVA_LIBRARIES += \
android-support-annotations \
- android-arch-lifecycle-common
+ apptoolkit-lifecycle-common
LOCAL_STATIC_ANDROID_LIBRARIES += \
android-support-v4 \
- android-arch-lifecycle-runtime \
+ apptoolkit-lifecycle-runtime \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
diff --git a/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml b/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml
index ac56a2d..bc330c7 100644
--- a/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml
+++ b/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml
@@ -22,25 +22,40 @@
android:fillViewport ="true"
android:orientation="vertical">
- <com.android.settingslib.notification.ZenRadioLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/zen_conditions"
+ <LinearLayout
+ android:id="@+id/dialog_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginEnd="4dp"
- android:layout_marginStart="4dp"
- android:paddingBottom="4dp"
- android:orientation="horizontal">
- <RadioGroup
- android:id="@+id/zen_radio_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <LinearLayout
- android:id="@+id/zen_radio_buttons_content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical"/>
- </com.android.settingslib.notification.ZenRadioLayout>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.settingslib.notification.ZenRadioLayout
+ android:id="@+id/zen_conditions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginStart="4dp"
+ android:paddingBottom="4dp"
+ android:orientation="horizontal">
+ <RadioGroup
+ android:id="@+id/zen_radio_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <LinearLayout
+ android:id="@+id/zen_radio_buttons_content"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"/>
+ </com.android.settingslib.notification.ZenRadioLayout>
+
+ <TextView
+ android:id="@+id/zen_alarm_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="18dp"
+ android:layout_marginEnd="16dp"
+ android:textDirection="locale"
+ android:textColor="?android:attr/colorError"/>
+ </LinearLayout>
</ScrollView>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index c926e1f..c1aa2dc 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -250,6 +250,19 @@
<item>Best Effort (Adaptive Bit Rate)</item>
</string-array>
+ <!-- TODO: Enable for translation per b/73007419 -->
+ <!-- Summaries for Bluetooth Audio Active Device status. [CHAR LIMIT=50]-->
+ <string-array name="bluetooth_audio_active_device_summaries" translatable="false" >
+ <!-- Status message when the device is not Active. -->
+ <item></item>
+ <!-- Status message when the device is Active for Media and Phone. -->
+ <item>, active</item>
+ <!-- Status message when the device is Active for Media only. -->
+ <item>, active(media)</item>
+ <!-- Status message when the device is Active for Phone only. -->
+ <item>, active(phone)</item>
+ </string-array>
+
<!-- Titles for logd limit size selection preference. [CHAR LIMIT=14] -->
<string-array name="select_logd_size_titles">
<item>Off</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 486a9bb..e6f4ec6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -128,27 +128,27 @@
<!-- Bluetooth settings. Message when connecting to a device -->
<string name="bluetooth_connecting">Connecting\u2026</string>
<!-- Bluetooth settings. Message when connected to a device. [CHAR LIMIT=40] -->
- <string name="bluetooth_connected">Connected</string>
+ <string name="bluetooth_connected">Connected<xliff:g id="active_device">%1$s</xliff:g></string>
<!--Bluetooth settings screen, summary text under individual Bluetooth devices when pairing -->
<string name="bluetooth_pairing">Pairing\u2026</string>
<!-- Bluetooth settings. Message when connected to a device, except for phone audio. [CHAR LIMIT=40] -->
- <string name="bluetooth_connected_no_headset">Connected (no phone)</string>
+ <string name="bluetooth_connected_no_headset">Connected (no phone)<xliff:g id="active_device">%1$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for media audio. [CHAR LIMIT=40] -->
- <string name="bluetooth_connected_no_a2dp">Connected (no media)</string>
+ <string name="bluetooth_connected_no_a2dp">Connected (no media)<xliff:g id="active_device">%1$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for map. [CHAR LIMIT=40] -->
- <string name="bluetooth_connected_no_map">Connected (no message access)</string>
+ <string name="bluetooth_connected_no_map">Connected (no message access)<xliff:g id="active_device">%1$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] -->
- <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)</string>
+ <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)<xliff:g id="active_device">%1$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, showing remote device battery level. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_connected_battery_level">Connected, battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string>
+ <string name="bluetooth_connected_battery_level">Connected, battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g><xliff:g id="active_device">%2$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for phone audio, showing remote device battery level. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_connected_no_headset_battery_level">Connected (no phone), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string>
+ <string name="bluetooth_connected_no_headset_battery_level">Connected (no phone), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g><xliff:g id="active_device">%2$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for media audio, showing remote device battery level. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_connected_no_a2dp_battery_level">Connected (no media), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string>
+ <string name="bluetooth_connected_no_a2dp_battery_level">Connected (no media), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g><xliff:g id="active_device">%2$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for phone/media audio, showing remote device battery level. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_connected_no_headset_no_a2dp_battery_level">Connected (no phone or media), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string>
+ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level">Connected (no phone or media), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g><xliff:g id="active_device">%2$s</xliff:g></string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. -->
<string name="bluetooth_profile_a2dp">Media audio</string>
@@ -805,13 +805,17 @@
<item>Colors optimized for digital content</item>
</string-array>
- <!-- Settings item title for inactive apps [CHAR LIMIT=35] -->
- <string name="inactive_apps_title">Inactive apps</string>
+ <!-- Settings item title for apps in standby (limiting background activity) [CHAR LIMIT=35] -->
+ <string name="inactive_apps_title">Standby apps</string>
<!-- Settings item summary for inactive app [CHAR LIMIT=100] -->
<string name="inactive_app_inactive_summary">Inactive. Tap to toggle.</string>
<!-- Settings item summary for active app [CHAR LIMIT=100] -->
<string name="inactive_app_active_summary">Active. Tap to toggle.</string>
+ <!-- Settings item summary for standby bucket value of an app. [CHAR LIMIT=100] -->
+ <string name="standby_bucket_summary">App standby
+ state:<xliff:g id="bucket"> %s</xliff:g></string>
+
<!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
<string name="runningservices_settings_title">Running services</string>
<!-- Services settings screen, setting option summary for the user to go to the screen to view running services -->
@@ -865,30 +869,49 @@
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
- <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
- <string name="power_remaining_duration_only">About <xliff:g id="time">^1</xliff:g> left</string>
- <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging -->
- <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">^1</xliff:g> left based on your usage</string>
- <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
- <string name="power_remaining_charging_duration_only"><xliff:g id="time">^1</xliff:g> left until fully charged</string>
+ <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
+ <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging -->
+ <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">%1$s</xliff:g> left based on your usage</string>
+ <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
+ <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string>
- <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
- <string name="power_remaining_duration_only_short"><xliff:g id="time">^1</xliff:g> left</string>
- <!-- [CHAR_LIMIT=60] Short label for estimated remaining duration of battery charging/discharging -->
- <string name="power_remaining_duration_only_short_enhanced"><xliff:g id="time">^1</xliff:g> left based on your usage</string>
+ <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
+ <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
- <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
- <string name="power_discharging_duration"><xliff:g id="level">^1</xliff:g> - about <xliff:g id="time">^2</xliff:g> left</string>
- <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate -->
- <string name="power_discharging_duration_enhanced"><xliff:g id="level">^1</xliff:g> - about <xliff:g id="time">^2</xliff:g> left based on your usage</string>
+ <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount -->
+ <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string>
+ <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount with the percentage -->
+ <string name="power_remaining_less_than_duration"><xliff:g id="level">%1$s</xliff:g> - Less than <xliff:g id="threshold">%2$s</xliff:g> remaining</string>
- <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
- <string name="power_discharging_duration_short"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g> left</string>
+ <!-- Used to let users know that they have more than some amount of battery life remaining with percentage. ex: 75% - more than 1 day remaining [CHAR LIMIT = 80] -->
+ <string name="power_remaining_more_than_subtext"><xliff:g id="level">%1$s</xliff:g>more than <xliff:g id="time_remaining">%2$s</xliff:g> remaining</string>
+ <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] -->
+ <string name="power_remaining_only_more_than_subtext">more than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string>
+
+ <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
+ <string name="power_remaining_duration_only_shutdown_imminent" product="default">phone may shutdown soon</string>
+ <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
+ <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">tablet may shutdown soon</string>
+ <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
+ <string name="power_remaining_duration_only_shutdown_imminent" product="device">device may shutdown soon</string>
+
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
+ <string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g> - about <xliff:g id="time">%2$s</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate -->
+ <string name="power_discharging_duration_enhanced"><xliff:g id="level">%1$s</xliff:g> - about <xliff:g id="time">%2$s</xliff:g> left based on your usage</string>
+
+ <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
+ <string name="power_remaining_duration_shutdown_imminent" product="default"><xliff:g id="level">%1$s</xliff:g> - phone may shutdown soon</string>
+ <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
+ <string name="power_remaining_duration_shutdown_imminent" product="tablet"><xliff:g id="level">%1$s</xliff:g> - tablet may shutdown soon</string>
+ <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
+ <string name="power_remaining_duration_shutdown_imminent" product="device"><xliff:g id="level">%1$s</xliff:g> - device may shutdown soon</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
<string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g> until fully charged</string>
+ <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until fully charged</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
@@ -1039,5 +1062,13 @@
<string name="zen_interruption_level_priority">Priority only</string>
<!-- [CHAR LIMIT=20] Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. -->
<string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string>
+ <!-- Warning text when an alarm might be silenced by Do Not Disturb [CHAR LIMIT=NONE] -->
+ <string name="zen_alarm_warning_indef">You won\'t hear your next alarm <xliff:g id="when" example="at 7:00 AM">%1$s</xliff:g> unless you turn this off before then</string>
+ <!-- Warning text when an alarm might be silenced by Do Not Disturb due to a time-based condition [CHAR LIMIT=NONE] -->
+ <string name="zen_alarm_warning">You won\'t hear your next alarm <xliff:g id="when" example="at 7:00 AM">%1$s</xliff:g></string>
+ <!-- Alarm template for near alarms [CHAR LIMIT=25] -->
+ <string name="alarm_template">at <xliff:g id="when" example="7:00 AM">%1$s</xliff:g></string>
+ <!-- Alarm template for far in the future alarms [CHAR LIMIT=25] -->
+ <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index fb0f75b..e1ebbc4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -940,60 +940,55 @@
com.android.settingslib.Utils.formatPercentage(batteryLevel);
}
- // TODO: A temporary workaround solution using string description the device is active.
- // Issue tracked by b/72317067 .
- // An alternative solution would be visual indication.
- // Intentionally not adding the strings to strings.xml for now:
- // 1) If this is just a short-term solution, no need to waste translation effort
- // 2) The number of strings with all possible combinations becomes enormously large.
- // If string description becomes part of the final solution, we MUST NOT
- // concatenate the strings here: this does not translate well.
- String activeString = null;
+ // Prepare the string for the Active Device summary
+ String[] activeDeviceStringsArray = mContext.getResources().getStringArray(
+ R.array.bluetooth_audio_active_device_summaries);
+ String activeDeviceString = activeDeviceStringsArray[0]; // Default value: not active
if (mIsActiveDeviceA2dp && mIsActiveDeviceHeadset) {
- activeString = ", active";
+ activeDeviceString = activeDeviceStringsArray[1]; // Active for Media and Phone
} else {
if (mIsActiveDeviceA2dp) {
- activeString = ", active(media)";
+ activeDeviceString = activeDeviceStringsArray[2]; // Active for Media only
}
if (mIsActiveDeviceHeadset) {
- activeString = ", active(phone)";
+ activeDeviceString = activeDeviceStringsArray[3]; // Active for Phone only
}
}
- if (activeString == null) activeString = "";
if (profileConnected) {
if (a2dpNotConnected && hfpNotConnected) {
if (batteryLevelPercentageString != null) {
return mContext.getString(
R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
- batteryLevelPercentageString) + activeString;
+ batteryLevelPercentageString, activeDeviceString);
} else {
- return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp) +
- activeString;
+ return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp,
+ activeDeviceString);
}
} else if (a2dpNotConnected) {
if (batteryLevelPercentageString != null) {
return mContext.getString(R.string.bluetooth_connected_no_a2dp_battery_level,
- batteryLevelPercentageString) + activeString;
+ batteryLevelPercentageString, activeDeviceString);
} else {
- return mContext.getString(R.string.bluetooth_connected_no_a2dp) + activeString;
+ return mContext.getString(R.string.bluetooth_connected_no_a2dp,
+ activeDeviceString);
}
} else if (hfpNotConnected) {
if (batteryLevelPercentageString != null) {
return mContext.getString(R.string.bluetooth_connected_no_headset_battery_level,
- batteryLevelPercentageString) + activeString;
+ batteryLevelPercentageString, activeDeviceString);
} else {
- return mContext.getString(R.string.bluetooth_connected_no_headset)
- + activeString;
+ return mContext.getString(R.string.bluetooth_connected_no_headset,
+ activeDeviceString);
}
} else {
if (batteryLevelPercentageString != null) {
return mContext.getString(R.string.bluetooth_connected_battery_level,
- batteryLevelPercentageString) + activeString;
+ batteryLevelPercentageString, activeDeviceString);
} else {
- return mContext.getString(R.string.bluetooth_connected) + activeString;
+ return mContext.getString(R.string.bluetooth_connected, activeDeviceString);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
old mode 100755
new mode 100644
index cda4e45..5f7ba586
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -194,8 +194,13 @@
return mState;
}
- synchronized void setBluetoothStateInt(int state) {
- mState = state;
+ void setBluetoothStateInt(int state) {
+ synchronized(this) {
+ if (mState == state) {
+ return;
+ }
+ mState = state;
+ }
if (state == BluetoothAdapter.STATE_ON) {
// if mProfileManager hasn't been constructed yet, it will
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java
index 4e78d9b..85bf4e8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.Build;
+import android.os.UserManager;
import android.provider.Settings;
import android.support.v4.content.LocalBroadcastManager;
@@ -27,7 +28,8 @@
public static final String DEVELOPMENT_SETTINGS_CHANGED_ACTION =
"com.android.settingslib.development.DevelopmentSettingsEnabler.SETTINGS_CHANGED";
- private DevelopmentSettingsEnabler() {}
+ private DevelopmentSettingsEnabler() {
+ }
public static void setDevelopmentSettingsEnabled(Context context, boolean enable) {
Settings.Global.putInt(context.getContentResolver(),
@@ -37,8 +39,14 @@
}
public static boolean isDevelopmentSettingsEnabled(Context context) {
- return Settings.Global.getInt(context.getContentResolver(),
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ final boolean settingEnabled = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
Build.TYPE.equals("eng") ? 1 : 0) != 0;
+ final boolean hasRestriction = um.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES);
+ final boolean isAdmin = um.isAdminUser();
+
+ return isAdmin && !hasRestriction && settingEnabled;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 190f5e6..68ead09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -17,7 +17,6 @@
import android.annotation.LayoutRes;
import android.annotation.Nullable;
-import android.app.ActionBar;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -72,9 +71,9 @@
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
super.setContentView(R.layout.settings_with_drawer);
- mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);
+ mContentHeaderContainer = findViewById(R.id.content_header_container);
- Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+ Toolbar toolbar = findViewById(R.id.action_bar);
if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
toolbar.setVisibility(View.GONE);
return;
@@ -89,7 +88,9 @@
@Override
public boolean onNavigateUp() {
- finish();
+ if (!super.onNavigateUp()) {
+ finish();
+ }
return true;
}
@@ -104,11 +105,6 @@
registerReceiver(mPackageReceiver, filter);
new CategoriesUpdateTask().execute();
- final Intent intent = getIntent();
- if (intent != null && intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) {
- // Intent explicitly set to show menu.
- showMenuIcon();
- }
}
@Override
@@ -125,13 +121,6 @@
mCategoryListeners.remove(listener);
}
- public void setContentHeaderView(View headerView) {
- mContentHeaderContainer.removeAllViews();
- if (headerView != null) {
- mContentHeaderContainer.addView(headerView);
- }
- }
-
@Override
public void setContentView(@LayoutRes int layoutResID) {
final ViewGroup parent = findViewById(R.id.content_frame);
@@ -151,13 +140,6 @@
((ViewGroup) findViewById(R.id.content_frame)).addView(view, params);
}
- private void showMenuIcon() {
- final ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- }
- }
-
private void onCategoriesChanged() {
final int N = mCategoryListeners.size();
for (int i = 0; i < N; i++) {
@@ -165,10 +147,6 @@
}
}
- public void onProfileTileOpen() {
- finish();
- }
-
/**
* @return whether or not the enabled state actually changed.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index ed3696c..f7aa297 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -158,6 +158,9 @@
usage.startDate = start;
usage.usageLevel = totalBytes;
usage.period = formatDateRange(start, end);
+ usage.cycleStart = start;
+ usage.cycleEnd = end;
+
if (policy != null) {
usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
@@ -245,6 +248,8 @@
public long limitLevel;
public long warningLevel;
public long usageLevel;
+ public long cycleStart;
+ public long cycleEnd;
}
public interface Callback {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index a20f687..1a54d6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -1,5 +1,3 @@
-package com.android.settingslib.notification;
-
/*
* Copyright (C) 2018 The Android Open Source Project
*
@@ -16,6 +14,8 @@
* limitations under the License.
*/
+package com.android.settingslib.notification;
+
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
@@ -28,6 +28,7 @@
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
+import android.text.format.DateFormat;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
@@ -40,6 +41,7 @@
import android.widget.ScrollView;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.policy.PhoneWindow;
@@ -48,11 +50,11 @@
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
+import java.util.Locale;
import java.util.Objects;
public class EnableZenModeDialog {
-
- private static final String TAG = "QSEnableZenModeDialog";
+ private static final String TAG = "EnableZenModeDialog";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@@ -60,25 +62,37 @@
private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
- private static final int FOREVER_CONDITION_INDEX = 0;
- private static final int COUNTDOWN_CONDITION_INDEX = 1;
- private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
+ @VisibleForTesting
+ protected static final int FOREVER_CONDITION_INDEX = 0;
+ @VisibleForTesting
+ protected static final int COUNTDOWN_CONDITION_INDEX = 1;
+ @VisibleForTesting
+ protected static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
private static final int SECONDS_MS = 1000;
private static final int MINUTES_MS = 60 * SECONDS_MS;
- private Uri mForeverId;
+ @VisibleForTesting
+ protected Uri mForeverId;
private int mBucketIndex = -1;
private AlarmManager mAlarmManager;
private int mUserId;
private boolean mAttached;
- private Context mContext;
+ @VisibleForTesting
+ protected Context mContext;
+ @VisibleForTesting
+ protected TextView mZenAlarmWarning;
+ @VisibleForTesting
+ protected LinearLayout mZenRadioGroupContent;
+
private RadioGroup mZenRadioGroup;
- private LinearLayout mZenRadioGroupContent;
private int MAX_MANUAL_DND_OPTIONS = 3;
+ @VisibleForTesting
+ protected LayoutInflater mLayoutInflater;
+
public EnableZenModeDialog(Context context) {
mContext = context;
}
@@ -133,32 +147,40 @@
for (int i = 0; i < N; i++) {
mZenRadioGroupContent.getChildAt(i).setVisibility(View.GONE);
}
+
+ mZenAlarmWarning.setVisibility(View.GONE);
}
protected View getContentView() {
- final LayoutInflater inflater = new PhoneWindow(mContext).getLayoutInflater();
- View contentView = inflater.inflate(R.layout.zen_mode_turn_on_dialog_container, null);
+ if (mLayoutInflater == null) {
+ mLayoutInflater = new PhoneWindow(mContext).getLayoutInflater();
+ }
+ View contentView = mLayoutInflater.inflate(R.layout.zen_mode_turn_on_dialog_container,
+ null);
ScrollView container = (ScrollView) contentView.findViewById(R.id.container);
mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons);
mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content);
+ mZenAlarmWarning = container.findViewById(R.id.zen_alarm_warning);
for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) {
- final View radioButton = inflater.inflate(R.layout.zen_mode_radio_button,
+ final View radioButton = mLayoutInflater.inflate(R.layout.zen_mode_radio_button,
mZenRadioGroup, false);
mZenRadioGroup.addView(radioButton);
radioButton.setId(i);
- final View radioButtonContent = inflater.inflate(R.layout.zen_mode_condition,
+ final View radioButtonContent = mLayoutInflater.inflate(R.layout.zen_mode_condition,
mZenRadioGroupContent, false);
radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS);
mZenRadioGroupContent.addView(radioButtonContent);
}
+
hideAllConditions();
return contentView;
}
- private void bind(final Condition condition, final View row, final int rowId) {
+ @VisibleForTesting
+ protected void bind(final Condition condition, final View row, final int rowId) {
if (condition == null) throw new IllegalArgumentException("condition must not be null");
final boolean enabled = condition.state == Condition.STATE_TRUE;
final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() :
@@ -181,6 +203,7 @@
if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId);
MetricsLogger.action(mContext,
MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
+ updateAlarmWarningText(tag.condition);
announceConditionSelection(tag);
}
}
@@ -190,11 +213,13 @@
row.setVisibility(View.VISIBLE);
}
- private ConditionTag getConditionTagAt(int index) {
+ @VisibleForTesting
+ protected ConditionTag getConditionTagAt(int index) {
return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
}
- private void bindConditions(Condition c) {
+ @VisibleForTesting
+ protected void bindConditions(Condition c) {
// forever
bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
FOREVER_CONDITION_INDEX);
@@ -236,11 +261,13 @@
return info != null ? info.getTriggerTime() : 0;
}
- private boolean isAlarm(Condition c) {
+ @VisibleForTesting
+ protected boolean isAlarm(Condition c) {
return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id);
}
- private boolean isCountdown(Condition c) {
+ @VisibleForTesting
+ protected boolean isCountdown(Condition c) {
return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
}
@@ -264,7 +291,8 @@
}
// Returns a time condition if the next alarm is within the next week.
- private Condition getTimeUntilNextAlarmCondition() {
+ @VisibleForTesting
+ protected Condition getTimeUntilNextAlarmCondition() {
GregorianCalendar weekRange = new GregorianCalendar();
setToMidnight(weekRange);
weekRange.add(Calendar.DATE, 6);
@@ -282,7 +310,8 @@
return null;
}
- private void bindGenericCountdown() {
+ @VisibleForTesting
+ protected void bindGenericCountdown() {
mBucketIndex = DEFAULT_BUCKET_INDEX;
Condition countdown = ZenModeConfig.toTimeCondition(mContext,
MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
@@ -366,7 +395,8 @@
}
}
- private void bindNextAlarm(Condition c) {
+ @VisibleForTesting
+ protected void bindNextAlarm(Condition c) {
View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX);
ConditionTag tag = (ConditionTag) alarmContent.getTag();
@@ -415,6 +445,7 @@
MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
}
bind(newCondition, row, rowId);
+ updateAlarmWarningText(tag.condition);
tag.rb.setChecked(true);
announceConditionSelection(tag);
}
@@ -428,8 +459,43 @@
}
}
+ private void updateAlarmWarningText(Condition condition) {
+ String warningText = computeAlarmWarningText(condition);
+ mZenAlarmWarning.setText(warningText);
+ mZenAlarmWarning.setVisibility(warningText == null ? View.GONE : View.VISIBLE);
+ }
+
+ private String computeAlarmWarningText(Condition condition) {
+ final long now = System.currentTimeMillis();
+ final long nextAlarm = getNextAlarm();
+ if (nextAlarm < now) {
+ return null;
+ }
+ int warningRes = 0;
+ if (condition == null || isForever(condition)) {
+ warningRes = R.string.zen_alarm_warning_indef;
+ } else {
+ final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
+ if (time > now && nextAlarm < time) {
+ warningRes = R.string.zen_alarm_warning;
+ }
+ }
+ if (warningRes == 0) {
+ return null;
+ }
+ final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000;
+ final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+ final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma");
+ final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+ final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm);
+ final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far;
+ final String template = mContext.getResources().getString(templateRes, formattedTime);
+ return mContext.getResources().getString(warningRes, template);
+ }
+
// used as the view tag on condition rows
- private static class ConditionTag {
+ @VisibleForTesting
+ protected static class ConditionTag {
public RadioButton rb;
public View lines;
public TextView line1;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
new file mode 100644
index 0000000..346ca66
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.utils;
+
+import android.content.Context;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import com.android.settingslib.R;
+import com.android.settingslib.utils.StringUtil;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/** Utility class for keeping power related strings consistent**/
+public class PowerUtil {
+ private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7);
+ private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15);
+ private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1);
+
+ /**
+ * This method produces the text used in various places throughout the system to describe the
+ * remaining battery life of the phone in a consistent manner.
+ *
+ * @param context
+ * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
+ * @param percentageString An optional percentage of battery remaining string.
+ * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
+ * @return a properly formatted and localized string describing how much time remains
+ * before the battery runs out.
+ */
+ public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
+ @Nullable String percentageString, boolean basedOnUsage) {
+ if (drainTimeMs > 0) {
+ if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
+ // show a imminent shutdown warning if less than 7 minutes remain
+ return getShutdownImminentString(context, percentageString);
+ } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
+ // show a less than 15 min remaining warning if appropriate
+ CharSequence timeString = StringUtil.formatElapsedTime(context,
+ FIFTEEN_MINUTES_MILLIS,
+ false /* withSeconds */);
+ return getUnderFifteenString(context, timeString, percentageString);
+ } else if (drainTimeMs >= ONE_DAY_MILLIS) {
+ // just say more than one day if over 24 hours
+ return getMoreThanOneDayString(context, percentageString);
+ } else {
+ // show a regular time remaining string
+ return getRegularTimeRemainingString(context, drainTimeMs,
+ percentageString, basedOnUsage);
+ }
+ }
+ return null;
+ }
+
+ private static String getShutdownImminentString(Context context, String percentageString) {
+ return TextUtils.isEmpty(percentageString)
+ ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
+ : context.getString(
+ R.string.power_remaining_duration_shutdown_imminent,
+ percentageString);
+ }
+
+ private static String getUnderFifteenString(Context context, CharSequence timeString,
+ String percentageString) {
+ return TextUtils.isEmpty(percentageString)
+ ? context.getString(R.string.power_remaining_less_than_duration_only, timeString)
+ : context.getString(
+ R.string.power_remaining_less_than_duration,
+ percentageString,
+ timeString);
+
+ }
+
+ private static String getMoreThanOneDayString(Context context, String percentageString) {
+ final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
+ final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
+
+ final Measure daysMeasure = new Measure(1, MeasureUnit.DAY);
+
+ return TextUtils.isEmpty(percentageString)
+ ? context.getString(R.string.power_remaining_only_more_than_subtext,
+ frmt.formatMeasures(daysMeasure))
+ : context.getString(
+ R.string.power_remaining_more_than_subtext,
+ percentageString,
+ frmt.formatMeasures(daysMeasure));
+ }
+
+ private static String getRegularTimeRemainingString(Context context, long drainTimeMs,
+ String percentageString, boolean basedOnUsage) {
+ // round to the nearest 15 min to not appear oversly precise
+ final long roundedTimeMs = roundToNearestThreshold(drainTimeMs,
+ FIFTEEN_MINUTES_MILLIS);
+ CharSequence timeString = StringUtil.formatElapsedTime(context,
+ roundedTimeMs,
+ false /* withSeconds */);
+ if (TextUtils.isEmpty(percentageString)) {
+ int id = basedOnUsage
+ ? R.string.power_remaining_duration_only_enhanced
+ : R.string.power_remaining_duration_only;
+ return context.getString(id, timeString);
+ } else {
+ int id = basedOnUsage
+ ? R.string.power_discharging_duration_enhanced
+ : R.string.power_discharging_duration;
+ return context.getString(id, percentageString, timeString);
+ }
+ }
+
+ public static long convertUsToMs(long timeUs) {
+ return timeUs / 1000;
+ }
+
+ public static long convertMsToUs(long timeMs) {
+ return timeMs * 1000;
+ }
+
+ private static long roundToNearestThreshold(long drainTime, long threshold) {
+ final long remainder = drainTime % threshold;
+ if (remainder < threshold / 2) {
+ return drainTime - remainder;
+ } else {
+ return drainTime - remainder + threshold;
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
new file mode 100644
index 0000000..45fdd78
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.utils;
+
+import android.content.Context;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.text.RelativeDateTimeFormatter;
+import android.icu.text.RelativeDateTimeFormatter.RelativeUnit;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.icu.util.ULocale;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.TtsSpan;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/** Utility class for generally useful string methods **/
+public class StringUtil {
+
+ public static final int SECONDS_PER_MINUTE = 60;
+ public static final int SECONDS_PER_HOUR = 60 * 60;
+ public static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+ /**
+ * Returns elapsed time for the given millis, in the following format:
+ * 2d 5h 40m 29s
+ * @param context the application context
+ * @param millis the elapsed time in milli seconds
+ * @param withSeconds include seconds?
+ * @return the formatted elapsed time
+ */
+ public static CharSequence formatElapsedTime(Context context, double millis,
+ boolean withSeconds) {
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+ int seconds = (int) Math.floor(millis / 1000);
+ if (!withSeconds) {
+ // Round up.
+ seconds += 30;
+ }
+
+ int days = 0, hours = 0, minutes = 0;
+ if (seconds >= SECONDS_PER_DAY) {
+ days = seconds / SECONDS_PER_DAY;
+ seconds -= days * SECONDS_PER_DAY;
+ }
+ if (seconds >= SECONDS_PER_HOUR) {
+ hours = seconds / SECONDS_PER_HOUR;
+ seconds -= hours * SECONDS_PER_HOUR;
+ }
+ if (seconds >= SECONDS_PER_MINUTE) {
+ minutes = seconds / SECONDS_PER_MINUTE;
+ seconds -= minutes * SECONDS_PER_MINUTE;
+ }
+
+ final ArrayList<Measure> measureList = new ArrayList(4);
+ if (days > 0) {
+ measureList.add(new Measure(days, MeasureUnit.DAY));
+ }
+ if (hours > 0) {
+ measureList.add(new Measure(hours, MeasureUnit.HOUR));
+ }
+ if (minutes > 0) {
+ measureList.add(new Measure(minutes, MeasureUnit.MINUTE));
+ }
+ if (withSeconds && seconds > 0) {
+ measureList.add(new Measure(seconds, MeasureUnit.SECOND));
+ }
+ if (measureList.size() == 0) {
+ // Everything addable was zero, so nothing was added. We add a zero.
+ measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE));
+ }
+ final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]);
+
+ final Locale locale = context.getResources().getConfiguration().locale;
+ final MeasureFormat measureFormat = MeasureFormat.getInstance(
+ locale, FormatWidth.NARROW);
+ sb.append(measureFormat.formatMeasures(measureArray));
+
+ if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) {
+ // Add ttsSpan if it only have minute value, because it will be read as "meters"
+ final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes)
+ .setUnit("minute").build();
+ sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ return sb;
+ }
+
+ /**
+ * Returns relative time for the given millis in the past, in a short format such as "2 days
+ * ago", "5 hr. ago", "40 min. ago", or "29 sec. ago".
+ *
+ * <p>The unit is chosen to have good information value while only using one unit. So 27 hours
+ * and 50 minutes would be formatted as "28 hr. ago", while 50 hours would be formatted as
+ * "2 days ago".
+ *
+ * @param context the application context
+ * @param millis the elapsed time in milli seconds
+ * @param withSeconds include seconds?
+ * @return the formatted elapsed time
+ */
+ public static CharSequence formatRelativeTime(Context context, double millis,
+ boolean withSeconds) {
+ final int seconds = (int) Math.floor(millis / 1000);
+ final RelativeUnit unit;
+ final int value;
+ if (withSeconds && seconds < 2 * SECONDS_PER_MINUTE) {
+ unit = RelativeUnit.SECONDS;
+ value = seconds;
+ } else if (seconds < 2 * SECONDS_PER_HOUR) {
+ unit = RelativeUnit.MINUTES;
+ value = (seconds + SECONDS_PER_MINUTE / 2)
+ / SECONDS_PER_MINUTE;
+ } else if (seconds < 2 * SECONDS_PER_DAY) {
+ unit = RelativeUnit.HOURS;
+ value = (seconds + SECONDS_PER_HOUR / 2)
+ / SECONDS_PER_HOUR;
+ } else {
+ unit = RelativeUnit.DAYS;
+ value = (seconds + SECONDS_PER_DAY / 2)
+ / SECONDS_PER_DAY;
+ }
+
+ final Locale locale = context.getResources().getConfiguration().locale;
+ final RelativeDateTimeFormatter formatter = RelativeDateTimeFormatter.getInstance(
+ ULocale.forLocale(locale),
+ null /* default NumberFormat */,
+ RelativeDateTimeFormatter.Style.SHORT,
+ android.icu.text.DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE);
+
+ return formatter.format(value, RelativeDateTimeFormatter.Direction.LAST, unit);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 109eb97..8115ede2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -41,7 +41,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.AccessPoint.Speed;
-public class AccessPointPreference extends TwoTargetPreference {
+public class AccessPointPreference extends Preference {
private static final int[] STATE_SECURED = {
R.attr.state_encrypted
@@ -115,6 +115,7 @@
int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld,
int level, IconInjector iconInjector) {
super(context);
+ setWidgetLayoutResource(R.layout.access_point_friction_widget);
mBadgeCache = cache;
mAccessPoint = accessPoint;
mForSavedNetworks = forSavedNetworks;
@@ -153,20 +154,6 @@
ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
bindFrictionImage(frictionImageView);
- setDividerVisibility(view, View.GONE);
- }
-
- protected void setDividerVisibility(final PreferenceViewHolder view,
- @View.Visibility int visibility) {
- final View divider = view.findViewById(R.id.two_target_divider);
- if (divider != null) {
- divider.setVisibility(visibility);
- }
- }
-
- @Override
- protected int getSecondTargetResId() {
- return R.layout.access_point_friction_widget;
}
protected void updateIcon(int level, Context context) {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
index 003f905..2f417ad 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
@@ -18,8 +18,6 @@
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import android.app.Instrumentation;
@@ -49,42 +47,22 @@
}
@Test
- public void startActivityWithNoExtra_showNoNavUp() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.startActivitySync(new Intent(instrumentation.getTargetContext(),
- TestActivity.class));
-
- onView(withContentDescription(com.android.internal.R.string.action_bar_up_description))
- .check(doesNotExist());
- }
-
- @Test
- public void startActivityWithExtraToHideMenu_showNavUp() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
- .putExtra(TestActivity.EXTRA_SHOW_MENU, false);
+ public void startActivity_doNotShowNavUp() {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
instrumentation.startActivitySync(intent);
onView(withContentDescription(com.android.internal.R.string.action_bar_up_description))
.check(doesNotExist());
}
- @Test
- public void startActivityWithExtraToShowMenu_showNavUp() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
- .putExtra(TestActivity.EXTRA_SHOW_MENU, true);
- instrumentation.startActivitySync(intent);
-
- onView(withContentDescription(com.android.internal.R.string.action_bar_up_description))
- .check(matches(isDisplayed()));
- }
-
/**
* Test Activity in this test.
*
* Use this activity because SettingsDrawerActivity hasn't been registered in its
* AndroidManifest.xml
*/
- public static class TestActivity extends SettingsDrawerActivity {}
+ public static class TestActivity extends SettingsDrawerActivity {
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 4091ce1..1481161 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -80,22 +80,12 @@
doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
}
- /**
- * Test to verify the current test context object works so that we are not checking null
- * against null
- */
- @Test
- public void testContextMock() {
- assertThat(mContext.getString(R.string.bluetooth_connected)).isEqualTo("Connected");
- }
-
@Test
public void testGetConnectionSummary_testSingleProfileConnectDisconnect() {
// Test without battery level
// Set PAN profile to be connected and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
// Set PAN profile to be disconnected and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
@@ -105,9 +95,7 @@
mBatteryLevel = 10;
// Set PAN profile to be connected and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected_battery_level,
- com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, battery 10%");
// Set PAN profile to be disconnected and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
@@ -118,8 +106,7 @@
// Set PAN profile to be connected and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
// Set PAN profile to be disconnected and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
@@ -134,28 +121,23 @@
mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected_battery_level,
- com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, battery 10%");
// Disconnect HFP only and test connection state summary
mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected_no_headset_battery_level,
- com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected (no phone), battery 10%");
// Disconnect A2DP only and test connection state summary
mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected_no_a2dp_battery_level,
- com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected (no media), battery 10%");
// Disconnect both HFP and A2DP and test connection state summary
mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
- assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
- R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
- com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected (no phone or media), battery 10%");
// Disconnect all profiles and test connection state summary
mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
@@ -163,6 +145,117 @@
}
@Test
+ public void testGetConnectionSummary_testSingleProfileActiveDeviceA2dp() {
+ // Test without battery level
+ // Set A2DP profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
+
+ // Set device as Active for A2DP and test connection state summary
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(media)");
+
+ // Test with battery level
+ mBatteryLevel = 10;
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected, battery 10%, active(media)");
+
+ // Set A2DP profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+ // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
+ mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ // Set A2DP profile to be connected, Active and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(media)");
+
+ // Set A2DP profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+
+ @Test
+ public void testGetConnectionSummary_testSingleProfileActiveDeviceHfp() {
+ // Test without battery level
+ // Set HFP profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
+
+ // Set device as Active for HFP and test connection state summary
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(phone)");
+
+ // Test with battery level
+ mBatteryLevel = 10;
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected, battery 10%, active(phone)");
+
+ // Set HFP profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+ // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
+ mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ // Set HFP profile to be connected, Active and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(phone)");
+
+ // Set HFP profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+
+ @Test
+ public void testGetConnectionSummary_testMultipleProfilesActiveDevice() {
+ // Test without battery level
+ // Set A2DP and HFP profiles to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
+
+ // Set device as Active for A2DP and HFP and test connection state summary
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active");
+
+ // Test with battery level
+ mBatteryLevel = 10;
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected, battery 10%, active");
+
+ // Disconnect A2DP only and test connection state summary
+ mCachedDevice.setActiveDevice(false, BluetoothProfile.A2DP);
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected (no media), battery 10%, active(phone)");
+
+ // Disconnect HFP only and test connection state summary
+ mCachedDevice.setActiveDevice(false, BluetoothProfile.HEADSET);
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
+ "Connected (no phone), battery 10%, active(media)");
+
+ // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
+ mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ // Set A2DP and HFP profiles to be connected, Active and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+ mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active");
+
+ // Set A2DP and HFP profiles to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+
+ @Test
public void testDeviceName_testAliasNameAvailable() {
when(mDevice.getAliasName()).thenReturn(DEVICE_ALIAS);
when(mDevice.getName()).thenReturn(DEVICE_NAME);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
index d19d19a..708353e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
@@ -23,7 +23,9 @@
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.ShadowUserManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,7 +33,9 @@
import org.robolectric.annotation.Config;
@RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = {
+ ShadowUserManager.class
+})
public class DevelopmentSettingsEnablerTest {
private Context mContext;
@@ -39,10 +43,16 @@
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
+ ShadowUserManager.getShadow().setIsAdminUser(true);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowUserManager.getShadow().reset();
}
@Test
- public void testEnabling() {
+ public void isEnabled_settingsOn_noRestriction_isAdmin_shouldReturnTrue() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
@@ -54,7 +64,7 @@
}
@Test
- public void testDisabling() {
+ public void isEnabled_settingsOff_noRestriction_isAdmin_shouldReturnFalse() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
@@ -64,4 +74,13 @@
assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isFalse();
}
+
+ @Test
+ public void isEnabled_settingsOn_noRestriction_notAdmin_shouldReturnFalse() {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+ ShadowUserManager.getShadow().setIsAdminUser(false);
+
+ assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
new file mode 100644
index 0000000..777cd98
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.view.LayoutInflater;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class EnableZenModeDialogTest {
+ private EnableZenModeDialog mController;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Fragment mFragment;
+
+ private Context mShadowContext;
+ private LayoutInflater mLayoutInflater;
+ private Condition mCountdownCondition;
+ private Condition mAlarmCondition;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mShadowContext = RuntimeEnvironment.application;
+ when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mFragment.getContext()).thenReturn(mShadowContext);
+ mLayoutInflater = LayoutInflater.from(mShadowContext);
+
+ mController = spy(new EnableZenModeDialog(mContext));
+ mController.mContext = mContext;
+ mController.mLayoutInflater = mLayoutInflater;
+ mController.mForeverId = Condition.newId(mContext).appendPath("forever").build();
+ when(mContext.getString(com.android.internal.R.string.zen_mode_forever))
+ .thenReturn("testSummary");
+ mController.getContentView();
+
+ // these methods use static calls to ZenModeConfig which would normally fail in robotests,
+ // so instead do nothing:
+ doNothing().when(mController).bindGenericCountdown();
+ doReturn(null).when(mController).getTimeUntilNextAlarmCondition();
+ doReturn(0L).when(mController).getNextAlarm();
+ doNothing().when(mController).bindNextAlarm(any());
+
+ // as a result of doing nothing above, must bind manually:
+ Uri alarm = Condition.newId(mContext).appendPath("alarm").build();
+ mAlarmCondition = new Condition(alarm, "alarm", "", "", 0, 0, 0);
+ Uri countdown = Condition.newId(mContext).appendPath("countdown").build();
+ mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0);
+ mController.bind(mCountdownCondition,
+ mController.mZenRadioGroupContent.getChildAt(
+ EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX),
+ EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX);
+ mController.bind(mAlarmCondition,
+ mController.mZenRadioGroupContent.getChildAt(
+ EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX),
+ EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX);
+ }
+
+ @Test
+ public void testForeverChecked() {
+ mController.bindConditions(mController.forever());
+
+ assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+ .isChecked());
+ assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+ .isChecked());
+ assertFalse(mController.getConditionTagAt(
+ EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ }
+
+ @Test
+ public void testNoneChecked() {
+ mController.bindConditions(null);
+ assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+ .isChecked());
+ assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+ .isChecked());
+ assertFalse(mController.getConditionTagAt(
+ EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ }
+
+ @Test
+ public void testAlarmChecked() {
+ doReturn(false).when(mController).isCountdown(mAlarmCondition);
+ doReturn(true).when(mController).isAlarm(mAlarmCondition);
+
+ mController.bindConditions(mAlarmCondition);
+ assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+ .isChecked());
+ assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+ .isChecked());
+ assertTrue(mController.getConditionTagAt(
+ EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ }
+
+ @Test
+ public void testCountdownChecked() {
+ doReturn(false).when(mController).isAlarm(mCountdownCondition);
+ doReturn(true).when(mController).isCountdown(mCountdownCondition);
+
+ mController.bindConditions(mCountdownCondition);
+ assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+ .isChecked());
+ assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+ .isChecked());
+ assertFalse(mController.getConditionTagAt(
+ EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
new file mode 100644
index 0000000..c8b3269
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils.shadow;
+
+import android.content.Context;
+import android.os.UserManager;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.shadow.api.Shadow;
+
+@Implements(UserManager.class)
+public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
+
+ private boolean mAdminUser;
+
+ public void setIsAdminUser(boolean isAdminUser) {
+ mAdminUser = isAdminUser;
+ }
+
+ @Resetter
+ public void reset() {
+ mAdminUser = false;
+ }
+
+ @Implementation
+ public boolean isAdminUser() {
+ return mAdminUser;
+ }
+
+ @Implementation
+ public static UserManager get(Context context) {
+ return (UserManager) context.getSystemService(Context.USER_SERVICE);
+ }
+
+ public static ShadowUserManager getShadow() {
+ return (ShadowUserManager) Shadow.extract(
+ RuntimeEnvironment.application.getSystemService(UserManager.class));
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
new file mode 100644
index 0000000..f93210f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import com.android.settingslib.R;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.utils.PowerUtil;
+import java.time.Duration;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PowerUtilTest {
+ public static final String TEST_BATTERY_LEVEL_10 = "10%";
+ public static final String FIFTEEN_MIN_FORMATTED = "15m";
+ public static final long SEVENTEEN_MIN_MILLIS = Duration.ofMinutes(17).toMillis();
+ public static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis();
+ public static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis();
+ public static final long TWO_DAYS_MILLIS = Duration.ofDays(2).toMillis();
+ public static final String ONE_DAY_FORMATTED = "1 day";
+
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_withPercentage() {
+ String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ SEVENTEEN_MIN_MILLIS,
+ TEST_BATTERY_LEVEL_10,
+ true /* basedOnUsage */);
+ String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ SEVENTEEN_MIN_MILLIS,
+ TEST_BATTERY_LEVEL_10,
+ false /* basedOnUsage */);
+
+ // We only add special mention for the long string
+ assertThat(info).isEqualTo(mContext.getString(
+ R.string.power_discharging_duration_enhanced,
+ TEST_BATTERY_LEVEL_10,
+ FIFTEEN_MIN_FORMATTED));
+ // shortened string should not have extra text
+ assertThat(info2).isEqualTo(mContext.getString(
+ R.string.power_discharging_duration,
+ TEST_BATTERY_LEVEL_10,
+ FIFTEEN_MIN_FORMATTED));
+ }
+
+ @Test
+ public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_noPercentage() {
+ String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ SEVENTEEN_MIN_MILLIS,
+ null /* percentageString */,
+ true /* basedOnUsage */);
+ String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ SEVENTEEN_MIN_MILLIS,
+ null /* percentageString */,
+ false /* basedOnUsage */);
+
+ // We only add special mention for the long string
+ assertThat(info).isEqualTo(mContext.getString(
+ R.string.power_remaining_duration_only_enhanced,
+ FIFTEEN_MIN_FORMATTED));
+ // shortened string should not have extra text
+ assertThat(info2).isEqualTo(mContext.getString(
+ R.string.power_remaining_duration_only,
+ FIFTEEN_MIN_FORMATTED));
+ }
+
+
+ @Test
+ public void testGetBatteryRemainingStringFormatted_lessThanSevenMinutes_usesCorrectString() {
+ String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ FIVE_MINUTES_MILLIS,
+ TEST_BATTERY_LEVEL_10 /* percentageString */,
+ true /* basedOnUsage */);
+ String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ FIVE_MINUTES_MILLIS,
+ null /* percentageString */,
+ true /* basedOnUsage */);
+
+ // additional battery percentage in this string
+ assertThat(info).isEqualTo(mContext.getString(
+ R.string.power_remaining_duration_shutdown_imminent,
+ TEST_BATTERY_LEVEL_10));
+ // shortened string should not have percentage
+ assertThat(info2).isEqualTo(mContext.getString(
+ R.string.power_remaining_duration_only_shutdown_imminent));
+ }
+
+ @Test
+ public void testGetBatteryRemainingStringFormatted_betweenSevenAndFifteenMinutes_usesCorrectString() {
+ String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ TEN_MINUTES_MILLIS,
+ null /* percentageString */,
+ true /* basedOnUsage */);
+ String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ TEN_MINUTES_MILLIS,
+ TEST_BATTERY_LEVEL_10 /* percentageString */,
+ true /* basedOnUsage */);
+
+ // shortened string should not have percentage
+ assertThat(info).isEqualTo(mContext.getString(
+ R.string.power_remaining_less_than_duration_only,
+ FIFTEEN_MIN_FORMATTED));
+ // Add percentage to string when provided
+ assertThat(info2).isEqualTo(mContext.getString(
+ R.string.power_remaining_less_than_duration,
+ TEST_BATTERY_LEVEL_10,
+ FIFTEEN_MIN_FORMATTED));
+ }
+
+ @Test
+ public void testGetBatteryRemainingStringFormatted_moreThanOneDay_usesCorrectString() {
+ String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ TWO_DAYS_MILLIS,
+ null /* percentageString */,
+ true /* basedOnUsage */);
+ String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext,
+ TWO_DAYS_MILLIS,
+ TEST_BATTERY_LEVEL_10 /* percentageString */,
+ true /* basedOnUsage */);
+
+ // shortened string should not have percentage
+ assertThat(info).isEqualTo(mContext.getString(
+ R.string.power_remaining_only_more_than_subtext,
+ ONE_DAY_FORMATTED));
+ // Add percentage to string when provided
+ assertThat(info2).isEqualTo(mContext.getString(
+ R.string.power_remaining_more_than_subtext,
+ TEST_BATTERY_LEVEL_10,
+ ONE_DAY_FORMATTED));
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
new file mode 100644
index 0000000..d5e3cdb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.text.SpannableStringBuilder;
+import android.text.format.DateUtils;
+import android.text.style.TtsSpan;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class StringUtilTest {
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = spy(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void testFormatElapsedTime_WithSeconds_ShowSeconds() {
+ final double testMillis = 5 * DateUtils.MINUTE_IN_MILLIS + 30 * DateUtils.SECOND_IN_MILLIS;
+ final String expectedTime = "5m 30s";
+
+ assertThat(StringUtil.formatElapsedTime(mContext, testMillis, true).toString())
+ .isEqualTo(expectedTime);
+ }
+
+ @Test
+ public void testFormatElapsedTime_NoSeconds_DoNotShowSeconds() {
+ final double testMillis = 5 * DateUtils.MINUTE_IN_MILLIS + 30 * DateUtils.SECOND_IN_MILLIS;
+ final String expectedTime = "6m";
+
+ assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+ .isEqualTo(expectedTime);
+ }
+
+ @Test
+ public void testFormatElapsedTime_TimeMoreThanOneDay_ShowCorrectly() {
+ final double testMillis = 2 * DateUtils.DAY_IN_MILLIS
+ + 4 * DateUtils.HOUR_IN_MILLIS + 15 * DateUtils.MINUTE_IN_MILLIS;
+ final String expectedTime = "2d 4h 15m";
+
+ assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+ .isEqualTo(expectedTime);
+ }
+
+ @Test
+ public void testFormatElapsedTime_ZeroFieldsInTheMiddleDontShow() {
+ final double testMillis = 2 * DateUtils.DAY_IN_MILLIS + 15 * DateUtils.MINUTE_IN_MILLIS;
+ final String expectedTime = "2d 15m";
+
+ assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+ .isEqualTo(expectedTime);
+ }
+
+ @Test
+ public void testFormatElapsedTime_FormatZero_WithSeconds() {
+ final double testMillis = 0;
+ final String expectedTime = "0s";
+
+ assertThat(StringUtil.formatElapsedTime(mContext, testMillis, true).toString())
+ .isEqualTo(expectedTime);
+ }
+
+ @Test
+ public void testFormatElapsedTime_FormatZero_NoSeconds() {
+ final double testMillis = 0;
+ final String expectedTime = "0m";
+
+ assertThat(StringUtil.formatElapsedTime(mContext, testMillis, false).toString())
+ .isEqualTo(expectedTime);
+ }
+
+ @Test
+ public void testFormatElapsedTime_onlyContainsMinute_hasTtsSpan() {
+ final double testMillis = 15 * DateUtils.MINUTE_IN_MILLIS;
+
+ final CharSequence charSequence =
+ StringUtil.formatElapsedTime(mContext, testMillis, false);
+ assertThat(charSequence).isInstanceOf(SpannableStringBuilder.class);
+
+ final SpannableStringBuilder expectedString = (SpannableStringBuilder) charSequence;
+ final TtsSpan[] ttsSpans = expectedString.getSpans(0, expectedString.length(),
+ TtsSpan.class);
+
+ assertThat(ttsSpans).asList().hasSize(1);
+ assertThat(ttsSpans[0].getType()).isEqualTo(TtsSpan.TYPE_MEASURE);
+ }
+
+ @Test
+ public void testFormatRelativeTime_WithSeconds_ShowSeconds() {
+ final double testMillis = 40 * DateUtils.SECOND_IN_MILLIS;
+ final String expectedTime = "40 sec. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_NoSeconds_DoNotShowSeconds() {
+ final double testMillis = 40 * DateUtils.SECOND_IN_MILLIS;
+ final String expectedTime = "1 min. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, false).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_LessThanTwoMinutes_withSeconds() {
+ final double testMillis = 119 * DateUtils.SECOND_IN_MILLIS;
+ final String expectedTime = "119 sec. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_LessThanTwoMinutes_NoSeconds() {
+ final double testMillis = 119 * DateUtils.SECOND_IN_MILLIS;
+ final String expectedTime = "2 min. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, false).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_TwoMinutes_withSeconds() {
+ final double testMillis = 2 * DateUtils.MINUTE_IN_MILLIS;
+ final String expectedTime = "2 min. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_LessThanTwoHours_withSeconds() {
+ final double testMillis = 119 * DateUtils.MINUTE_IN_MILLIS;
+ final String expectedTime = "119 min. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_TwoHours_withSeconds() {
+ final double testMillis = 2 * DateUtils.HOUR_IN_MILLIS;
+ final String expectedTime = "2 hr. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_LessThanTwoDays_withSeconds() {
+ final double testMillis = 47 * DateUtils.HOUR_IN_MILLIS;
+ final String expectedTime = "47 hr. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_TwoDays_withSeconds() {
+ final double testMillis = 2 * DateUtils.DAY_IN_MILLIS;
+ final String expectedTime = "2 days ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_FormatZero_WithSeconds() {
+ final double testMillis = 0;
+ final String expectedTime = "0 sec. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, true).toString()).isEqualTo(
+ expectedTime);
+ }
+
+ @Test
+ public void testFormatRelativeTime_FormatZero_NoSeconds() {
+ final double testMillis = 0;
+ final String expectedTime = "0 min. ago";
+
+ assertThat(StringUtil.formatRelativeTime(mContext, testMillis, false).toString()).isEqualTo(
+ expectedTime);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 1dc8e46..cfcfb95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -43,7 +43,6 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.ims.ImsConfig;
import com.android.internal.content.PackageHelper;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
@@ -1504,7 +1503,7 @@
try {
stmt = db.compileStatement("INSERT OR REPLACE INTO global(name,value)"
+ " VALUES(?,?);");
- loadStringSetting(stmt, Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND,
R.string.def_wireless_charging_started_sound);
db.setTransactionSuccessful();
} finally {
@@ -2578,7 +2577,7 @@
R.string.def_car_dock_sound);
loadStringSetting(stmt, Settings.Global.CAR_UNDOCK_SOUND,
R.string.def_car_undock_sound);
- loadStringSetting(stmt, Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND,
R.string.def_wireless_charging_started_sound);
loadIntegerSetting(stmt, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 537e8dc..39f2b52 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -160,7 +160,7 @@
Settings.Global.POWER_SOUNDS_ENABLED,
GlobalSettingsProto.POWER_SOUNDS_ENABLED);
dumpSetting(s, p,
- Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ Settings.Global.CHARGING_STARTED_SOUND,
GlobalSettingsProto.WIRELESS_CHARGING_STARTED_SOUND);
dumpSetting(s, p,
Settings.Global.CHARGING_SOUNDS_ENABLED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index adb4dbf..85a579d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3017,7 +3017,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 153;
+ private static final int SETTINGS_VERSION = 154;
private final int mUserId;
@@ -3627,6 +3627,23 @@
currentVersion = 153;
}
+ if (currentVersion == 153) {
+ // Version 154: Read notification badge configuration from config.
+ // If user has already set the value, don't do anything.
+ final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
+ final Setting showNotificationBadges = systemSecureSettings.getSettingLocked(
+ Settings.Secure.NOTIFICATION_BADGING);
+ if (showNotificationBadges.isNull()) {
+ final boolean defaultValue = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_notificationBadging);
+ systemSecureSettings.insertSettingLocked(
+ Secure.NOTIFICATION_BADGING,
+ defaultValue ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 154;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 64b2ae6..79299aa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -44,6 +44,7 @@
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- System tool permissions granted to the shell. -->
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2bcf4ef..1e48213 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -56,7 +56,6 @@
SystemUI-proto
LOCAL_JAVA_LIBRARIES := telephony-common
-LOCAL_JAVA_LIBRARIES += android.car
LOCAL_PACKAGE_NAME := SystemUI
LOCAL_CERTIFICATE := platform
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9613a6a..cbb3e8f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,9 +176,6 @@
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
- <!-- To control car audio module volume -->
- <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
-
<!-- the ability to rename notifications posted by other apps -->
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
diff --git a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml b/packages/SystemUI/res/layout/recents_onboarding.xml
similarity index 96%
rename from packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
rename to packages/SystemUI/res/layout/recents_onboarding.xml
index b3d5c90..12f278a 100644
--- a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
+++ b/packages/SystemUI/res/layout/recents_onboarding.xml
@@ -30,7 +30,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:text="@string/recents_swipe_up_onboarding"
android:textColor="@android:color/white"
android:textSize="16sp"
android:drawableBottom="@drawable/ic_chevron_up"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e55c65a..a9021ae 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -901,13 +901,28 @@
<dimen name="fingerprint_dialog_fp_icon_size">60dp</dimen>
<dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen>
- <!-- WirelessCharging Animation values -->
- <!-- Starting text size of batteryLevel for wireless charging animation -->
- <dimen name="config_batteryLevelTextSizeStart" format="float">5.0</dimen>
- <!-- Ending text size of batteryLevel for wireless charging animation -->
- <dimen name="config_batteryLevelTextSizeEnd" format="float">32.0</dimen>
- <!-- Wireless Charging battery level text animation duration -->
- <integer name="config_batteryLevelTextAnimationDuration">400</integer>
+ <!-- Wireless Charging Animation values -->
+ <dimen name="wireless_charging_dots_radius_start">0dp</dimen>
+ <dimen name="wireless_charging_dots_radius_end">4dp</dimen>
+ <dimen name="wireless_charging_circle_radius_start">28dp</dimen>
+ <dimen name="wireless_charging_circle_radius_end">92dp</dimen>
+ <integer name="wireless_charging_angle_offset">20</integer>
+ <integer name="wireless_charging_scale_dots_duration">83</integer>
+ <integer name="wireless_charging_num_dots">20</integer>
+ <!-- Starting text size in dp of batteryLevel for wireless charging animation -->
+ <dimen name="wireless_charging_anim_battery_level_text_size_start">0dp</dimen>
+ <!-- Ending text size in dp of batteryLevel for wireless charging animation -->
+ <dimen name="wireless_charging_anim_battery_level_text_size_end">14dp</dimen>
+ <!-- time until battery info is at full opacity-->
+ <integer name="wireless_charging_anim_opacity_offset">80</integer>
+ <!-- duration batteryLevel opacity goes from 0 to 1 duration -->
+ <integer name="wireless_charging_battery_level_text_opacity_duration">117</integer>
+ <!-- battery text scale animation duration -->
+ <integer name="wireless_charging_battery_level_text_scale_animation_duration">367</integer>
+ <!--time until wireless charging animation starts to fade-->
+ <integer name="wireless_charging_fade_offset">920</integer>
+ <!-- duration wireless charging animation takes to full fade to 0 opacity -->
+ <integer name="wireless_charging_fade_duration">200</integer>
<!-- Wired charging on AOD, text animation duration -->
<integer name="wired_charging_aod_text_animation_duration_down">500</integer>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index f9aa821..2d30f4c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -47,7 +47,6 @@
<item type="id" name="qs_icon_tag"/>
<item type="id" name="qs_slash_tag"/>
<item type="id" name="scrim"/>
- <item type="id" name="scrim_blanking"/>
<item type="id" name="scrim_target"/>
<item type="id" name="scrim_alpha_start"/>
<item type="id" name="scrim_alpha_end"/>
@@ -58,6 +57,8 @@
<item type="id" name="notification_plugin"/>
<item type="id" name="transformation_start_x_tag"/>
<item type="id" name="transformation_start_y_tag"/>
+ <item type="id" name="transformation_start_actual_width"/>
+ <item type="id" name="transformation_start_actual_height"/>
<item type="id" name="transformation_start_scale_x_tag"/>
<item type="id" name="transformation_start_scale_y_tag"/>
<item type="id" name="continuous_clipping_tag"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fadcbcd..fc5ea458 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -842,8 +842,6 @@
<string name="recents_stack_action_button_label">Clear all</string>
<!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] -->
<string name="recents_drag_hint_message">Drag here to use split screen</string>
- <!-- Recents: Text that shows above the nav bar after launching a few apps. [CHAR LIMIT=NONE] -->
- <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
<!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 64fa9c6..cd9e126 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -21,9 +21,40 @@
oneway interface IOverviewProxy {
void onBind(in ISystemUiProxy sysUiProxy);
+
+ /**
+ * Proxies motion events from the nav bar in SystemUI to the OverviewProxyService. The sender
+ * guarantees the following order of events:
+ *
+ * Normal gesture: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, UP
+ * Quick switch: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, SWITCH
+ * Quick scrub: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, SCRUB_START, SCRUB_PROGRESS*, SCRUB_END
+ *
+ * Once quick switch/scrub is sent, then no further motion events will be provided.
+ */
void onMotionEvent(in MotionEvent event);
+
+ /**
+ * Sent when a user has quickly flinged on the nav bar to switch tasks. Once this event is sent
+ * the caller will stop sending any motion events.
+ */
void onQuickSwitch();
+
+ /**
+ * Sent when the user starts to actively scrub the nav bar to switch tasks. Once this event is
+ * sent the caller will stop sending any motion events.
+ */
void onQuickScrubStart();
+
+ /**
+ * Sent when the user stops actively scrubbing the nav bar to switch tasks. Once this event is
+ * sent the caller will stop sending any motion events.
+ */
void onQuickScrubEnd();
+
+ /**
+ * Sent for each movement over the nav bar while the user is scrubbing it to switch tasks. Once
+ * this event is sent the caller will stop sending any motion events.
+ */
void onQuickScrubProgress(float progress);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 98ede4e..4cf817e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -39,4 +39,9 @@
* Called when the overview service has started the recents animation.
*/
void onRecentsAnimationStarted();
+
+ /**
+ * Specifies the text to be shown for onboarding the new swipe-up gesture to access recents.
+ */
+ void setRecentsOnboardingText(CharSequence text);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 90e3b1e..62bd72f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -43,6 +43,7 @@
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -57,6 +58,7 @@
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
+import android.view.WindowManagerGlobal;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -271,11 +273,20 @@
runner = new IRecentsAnimationRunner.Stub() {
public void onAnimationStart(IRecentsAnimationController controller,
RemoteAnimationTarget[] apps) {
+ final Rect stableInsets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
+ onAnimationStart_New(controller, apps, stableInsets, null);
+ }
+
+ public void onAnimationStart_New(IRecentsAnimationController controller,
+ RemoteAnimationTarget[] apps, Rect homeContentInsets,
+ Rect minimizedHomeBounds) {
final RecentsAnimationControllerCompat controllerCompat =
new RecentsAnimationControllerCompat(controller);
final RemoteAnimationTargetCompat[] appsCompat =
RemoteAnimationTargetCompat.wrap(apps);
- animationHandler.onAnimationStart(controllerCompat, appsCompat);
+ animationHandler.onAnimationStart(controllerCompat, appsCompat,
+ homeContentInsets, minimizedHomeBounds);
}
public void onAnimationCanceled() {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index bf6179d..a473db1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -16,13 +16,15 @@
package com.android.systemui.shared.system;
+import android.graphics.Rect;
+
public interface RecentsAnimationListener {
/**
* Called when the animation into Recents can start. This call is made on the binder thread.
*/
void onAnimationStart(RecentsAnimationControllerCompat controller,
- RemoteAnimationTargetCompat[] apps);
+ RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds);
/**
* Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 3871980..b8c5049 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -16,6 +16,9 @@
package com.android.systemui.shared.system;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+
+import android.app.WindowConfiguration;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.RemoteAnimationTarget;
@@ -37,7 +40,10 @@
public final Point position;
public final Rect sourceContainerBounds;
+ private final RemoteAnimationTarget mTarget;
+
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
+ mTarget = app;
taskId = app.taskId;
mode = app.mode;
leash = new SurfaceControlCompat(app.leash);
@@ -56,4 +62,18 @@
}
return appsCompat;
}
+
+ /**
+ * TODO: Get as a method for compatibility (will move into ctor once Launcher updates)
+ */
+ public Rect getContentInsets() {
+ return mTarget.contentInsets;
+ }
+
+ /**
+ * TODO: Get as a method for compatibility (will move into ctor once Launcher updates)
+ */
+ public boolean isAssistantActivityType() {
+ return mTarget.windowConfiguration.getActivityType() == ACTIVITY_TYPE_ASSISTANT;
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/tests/Android.mk b/packages/SystemUI/shared/tests/Android.mk
index 239a4e3..1715983 100644
--- a/packages/SystemUI/shared/tests/Android.mk
+++ b/packages/SystemUI/shared/tests/Android.mk
@@ -41,7 +41,7 @@
testables \
truth-prebuilt \
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
# sign this with platform cert, so this test is allowed to inject key events into
# UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index b7e1d67..b30b0c5 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -66,6 +66,7 @@
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
+ private CharSequence mOnboardingText;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -105,6 +106,10 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ public void setRecentsOnboardingText(CharSequence text) {
+ mOnboardingText = text;
+ }
};
private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() {
@@ -223,8 +228,8 @@
return mOverviewProxy;
}
- public ComponentName getLauncherComponent() {
- return mLauncherComponentName;
+ public CharSequence getOnboardingText() {
+ return mOnboardingText;
}
private void disconnectFromLauncherService() {
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 9319bc6..adb4e33 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -49,7 +49,7 @@
Key.QS_NIGHTDISPLAY_ADDED,
Key.SEEN_MULTI_USER,
Key.NUM_APPS_LAUNCHED,
- Key.HAS_SWIPED_UP_FOR_RECENTS,
+ Key.HAS_SEEN_RECENTS_ONBOARDING,
})
public @interface Key {
@Deprecated
@@ -78,7 +78,7 @@
String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
String SEEN_MULTI_USER = "HasSeenMultiUser";
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
- String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents";
+ String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 348855b..afc9629 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
@@ -36,13 +35,18 @@
*/
public class WirelessChargingAnimation {
- public static final long DURATION = 1400;
+ public static final long DURATION = 1133;
private static final String TAG = "WirelessChargingView";
private static final boolean LOCAL_LOGV = false;
private final WirelessChargingView mCurrentWirelessChargingView;
private static WirelessChargingView mPreviousWirelessChargingView;
+ public interface Callback {
+ void onAnimationStarting();
+ void onAnimationEnded();
+ }
+
/**
* Constructs an empty WirelessChargingAnimation object. If looper is null,
* Looper.myLooper() is used. Must set
@@ -51,9 +55,9 @@
* @hide
*/
public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
- batteryLevel) {
+ batteryLevel, Callback callback) {
mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
- batteryLevel);
+ batteryLevel, callback);
}
/**
@@ -61,8 +65,8 @@
* @hide
*/
public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
- @Nullable Looper looper, int batteryLevel) {
- return new WirelessChargingAnimation(context, looper, batteryLevel);
+ @Nullable Looper looper, int batteryLevel, Callback callback) {
+ return new WirelessChargingAnimation(context, looper, batteryLevel, callback);
}
/**
@@ -95,8 +99,11 @@
private View mView;
private View mNextView;
private WindowManager mWM;
+ private Callback mCallback;
- public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel) {
+ public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel,
+ Callback callback) {
+ mCallback = callback;
mNextView = new WirelessChargingLayout(context, batteryLevel);
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
@@ -149,6 +156,8 @@
}
public void hide(long duration) {
+ mHandler.removeMessages(HIDE);
+
if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
}
@@ -169,18 +178,6 @@
context = mView.getContext();
}
mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- // We can resolve the Gravity here by using the Locale for getting
- // the layout direction
- final Configuration config = mView.getContext().getResources().getConfiguration();
- final int gravity = Gravity.getAbsoluteGravity(mGravity,
- config.getLayoutDirection());
- mParams.gravity = gravity;
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
- mParams.horizontalWeight = 1.0f;
- }
- if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
- mParams.verticalWeight = 1.0f;
- }
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = DURATION;
@@ -191,6 +188,9 @@
if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
try {
+ if (mCallback != null) {
+ mCallback.onAnimationStarting();
+ }
mWM.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
Slog.d(TAG, "Unable to add wireless charging view. " + e);
@@ -203,6 +203,9 @@
if (mView != null) {
if (mView.getParent() != null) {
if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ if (mCallback != null) {
+ mCallback.onAnimationEnded();
+ }
mWM.removeViewImmediate(mView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index c78ea56..8f87d64 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -16,13 +16,16 @@
package com.android.systemui.charging;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import java.text.NumberFormat;
@@ -52,10 +55,9 @@
init(c, attrs, -1);
}
- private void init(Context c, AttributeSet attrs, int batteryLevel) {
+ private void init(Context context, AttributeSet attrs, int batteryLevel) {
final int mBatteryLevel = batteryLevel;
-
- inflate(c, R.layout.wireless_charging_layout, this);
+ inflate(context, R.layout.wireless_charging_layout, this);
// where the circle animation occurs:
final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);
@@ -68,14 +70,57 @@
if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));
-
- ValueAnimator animator = ObjectAnimator.ofFloat(mPercentage, "textSize",
- getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeStart),
- getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeEnd));
-
- animator.setDuration((long) getContext().getResources().getInteger(
- R.integer.config_batteryLevelTextAnimationDuration));
- animator.start();
+ mPercentage.setAlpha(0);
}
+
+ final long chargingAnimationFadeStartOffset = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_fade_offset);
+ final long chargingAnimationFadeDuration = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_fade_duration);
+ final int batteryLevelTextSizeStart = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_anim_battery_level_text_size_start);
+ final int batteryLevelTextSizeEnd = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_anim_battery_level_text_size_end);
+
+ // Animation Scale: battery percentage text scales from 0% to 100%
+ ValueAnimator textSizeAnimator = ObjectAnimator.ofFloat(mPercentage, "textSize",
+ batteryLevelTextSizeStart, batteryLevelTextSizeEnd);
+ textSizeAnimator.setInterpolator(new PathInterpolator(0, 0, 0, 1));
+ textSizeAnimator.setDuration((long) context.getResources().getInteger(
+ R.integer.wireless_charging_battery_level_text_scale_animation_duration));
+
+ // Animation Opacity: battery percentage text transitions from 0 to 1 opacity
+ ValueAnimator textOpacityAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 0, 1);
+ textOpacityAnimator.setInterpolator(Interpolators.LINEAR);
+ textOpacityAnimator.setDuration((long) context.getResources().getInteger(
+ R.integer.wireless_charging_battery_level_text_opacity_duration));
+ textOpacityAnimator.setStartDelay((long) context.getResources().getInteger(
+ R.integer.wireless_charging_anim_opacity_offset));
+
+ // Animation Opacity: battery percentage text fades from 1 to 0 opacity
+ ValueAnimator textFadeAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 1, 0);
+ textFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ textFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ textFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // Animation Opacity: wireless charging circle animation fades from 1 to 0 opacity
+ ValueAnimator circleFadeAnimator = ObjectAnimator.ofFloat(mChargingView, "alpha",
+ 1, 0);
+ circleFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ circleFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ circleFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // Animation Opacity: secondary text animation fades from 1 to 0 opacity
+ ValueAnimator secondaryTextFadeAnimator = ObjectAnimator.ofFloat(mSecondaryText, "alpha",
+ 1, 0);
+ circleFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ secondaryTextFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ secondaryTextFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // play all animations together
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator,
+ circleFadeAnimator, secondaryTextFadeAnimator);
+ animatorSet.start();
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
index f5edf52..19c6dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
@@ -21,10 +21,10 @@
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
final class WirelessChargingView extends View {
@@ -33,21 +33,21 @@
private float mPathGone;
private float mInterpolatedPathGone;
private long mAnimationStartTime;
- private long mStartSpinCircleAnimationTime;
- private long mAnimationOffset = 500;
- private long mTotalAnimationDuration = WirelessChargingAnimation.DURATION - mAnimationOffset;
- private long mExpandingCircle = (long) (mTotalAnimationDuration * .9);
- private long mSpinCircleAnimationTime = mTotalAnimationDuration - mExpandingCircle;
+ private long mScaleDotsDuration;
- private boolean mFinishedAnimatingSpinningCircles = false;
+ private boolean mFinishedAnimatingDots = false;
+ private int mNumDots;
- private int mStartAngle = -90;
- private int mNumSmallCircles = 20;
- private int mSmallCircleRadius = 10;
+ private double mAngleOffset;
+ private double mCurrAngleOffset;
- private int mMainCircleStartRadius = 100;
- private int mMainCircleEndRadius = 230;
- private int mMainCircleCurrentRadius = mMainCircleStartRadius;
+ private int mDotsRadiusStart;
+ private int mDotsRadiusEnd;
+ private int mCurrDotRadius;
+
+ private int mMainCircleStartRadius;
+ private int mMainCircleEndRadius;
+ private int mMainCircleCurrentRadius;
private int mCenterX;
private int mCenterY;
@@ -72,8 +72,25 @@
public void init(Context context, AttributeSet attr) {
mContext = context;
+
+ mDotsRadiusStart = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_dots_radius_start);
+ mDotsRadiusEnd = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_dots_radius_end);
+
+ mMainCircleStartRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_circle_radius_start);
+ mMainCircleEndRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_circle_radius_end);
+ mMainCircleCurrentRadius = mMainCircleStartRadius;
+
+ mAngleOffset = context.getResources().getInteger(R.integer.wireless_charging_angle_offset);
+ mScaleDotsDuration = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_scale_dots_duration);
+ mNumDots = context.getResources().getInteger(R.integer.wireless_charging_num_dots);
+
setupPaint();
- mInterpolator = new DecelerateInterpolator();
+ mInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
private void setupPaint() {
@@ -92,64 +109,62 @@
updateDrawingParameters();
drawCircles(canvas);
- if (!mFinishedAnimatingSpinningCircles) {
+ if (!mFinishedAnimatingDots) {
invalidate();
}
}
/**
* Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
- * {@link WirelessChargingView#mNumSmallCircles} smaller circles
+ * {@link WirelessChargingView#mNumDots} smaller circles
* @param canvas
*/
private void drawCircles(Canvas canvas) {
mCenterX = canvas.getWidth() / 2;
mCenterY = canvas.getHeight() / 2;
- // angleOffset makes small circles look like they're moving around the main circle
- float angleOffset = mPathGone * 10;
-
- // draws mNumSmallCircles to compose a larger, main circle
- for (int circle = 0; circle < mNumSmallCircles; circle++) {
- double angle = ((mStartAngle + angleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
- / mNumSmallCircles));
+ // draws mNumDots to compose a larger, main circle
+ for (int circle = 0; circle < mNumDots; circle++) {
+ double angle = ((mCurrAngleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
+ / mNumDots));
int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
- mSmallCircleRadius));
+ mCurrDotRadius));
int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
- mSmallCircleRadius));
+ mCurrDotRadius));
- canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);
+ canvas.drawCircle(x, y, mCurrDotRadius, mPaint);
}
- if (mMainCircleCurrentRadius >= mMainCircleEndRadius && !isSpinCircleAnimationStarted()) {
- mStartSpinCircleAnimationTime = System.currentTimeMillis();
+ if (mMainCircleCurrentRadius >= mMainCircleEndRadius) {
+ mFinishedAnimatingDots = true;
}
-
- if (isSpinAnimationFinished()) {
- mFinishedAnimatingSpinningCircles = true;
- }
- }
-
- private boolean isSpinCircleAnimationStarted() {
- return mStartSpinCircleAnimationTime != 0;
- }
-
- private boolean isSpinAnimationFinished() {
- return isSpinCircleAnimationStarted() && System.currentTimeMillis() -
- mStartSpinCircleAnimationTime > mSpinCircleAnimationTime;
}
private void updateDrawingParameters() {
- mPathGone = getPathGone(System.currentTimeMillis());
+ long now = System.currentTimeMillis();
+ long timeSinceStart = now - mAnimationStartTime;
+ mPathGone = getPathGone(now);
mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
+ // Position Dots: update main circle radius (that the dots compose)
if (mPathGone < 1.0f) {
mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
(mMainCircleEndRadius - mMainCircleStartRadius));
} else {
mMainCircleCurrentRadius = mMainCircleEndRadius;
}
+
+ // Scale Dots: update dot radius
+ if (timeSinceStart < mScaleDotsDuration) {
+ mCurrDotRadius = mDotsRadiusStart + (int) (mInterpolator.getInterpolation((float)
+ timeSinceStart / mScaleDotsDuration) * (mDotsRadiusEnd - mDotsRadiusStart));
+ } else {
+ mCurrDotRadius = mDotsRadiusEnd;
+ }
+
+ // Rotation Dot Group: dots rotate from 0 to 20 degrees
+ mCurrAngleOffset = mAngleOffset * mPathGone;
}
/**
@@ -158,6 +173,6 @@
* For values > 1.0 the larger circle has been drawn and further animation can occur
*/
private float getPathGone(long now) {
- return (float) (now - mAnimationStartTime) / (mExpandingCircle);
+ return (float) (now - mAnimationStartTime) / (WirelessChargingAnimation.DURATION);
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index bfb3a6e..092f3d2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -64,7 +64,7 @@
createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
handler, wakeLock, machine),
createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
- new DozeScreenState(wrappedService, handler),
+ new DozeScreenState(wrappedService, handler, params),
createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
new DozeWallpaperState(context)
});
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 8ec6afc..6ff8e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -24,6 +24,7 @@
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.util.Preconditions;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -87,12 +88,11 @@
}
}
- int screenState() {
+ int screenState(DozeParameters parameters) {
switch (this) {
case UNINITIALIZED:
case INITIALIZED:
case DOZE:
- case DOZE_REQUEST_PULSE:
case DOZE_AOD_PAUSED:
return Display.STATE_OFF;
case DOZE_PULSING:
@@ -100,6 +100,9 @@
case DOZE_AOD:
case DOZE_AOD_PAUSING:
return Display.STATE_DOZE_SUSPEND;
+ case DOZE_REQUEST_PULSE:
+ return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF
+ : Display.STATE_ON;
default:
return Display.STATE_UNKNOWN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index bef9cb3..3053de3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -19,6 +19,8 @@
import android.os.Handler;
import android.view.Display;
+import com.android.systemui.statusbar.phone.DozeParameters;
+
/**
* Controls the screen when dozing.
*/
@@ -26,17 +28,20 @@
private final DozeMachine.Service mDozeService;
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
+ private final DozeParameters mParameters;
private int mPendingScreenState = Display.STATE_UNKNOWN;
- public DozeScreenState(DozeMachine.Service service, Handler handler) {
+ public DozeScreenState(DozeMachine.Service service, Handler handler,
+ DozeParameters parameters) {
mDozeService = service;
mHandler = handler;
+ mParameters = parameters;
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
- int screenState = newState.screenState();
+ int screenState = newState.screenState(mParameters);
if (newState == DozeMachine.State.FINISH) {
// Make sure not to apply the screen state after DozeService was destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index aa56694..3a2b12f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -26,10 +26,6 @@
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.icu.text.MeasureFormat;
-import android.icu.text.MeasureFormat.FormatWidth;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Handler;
@@ -37,11 +33,11 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
-import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.Utils;
+import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -49,8 +45,6 @@
import java.io.PrintWriter;
import java.text.NumberFormat;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final String TAG = PowerUI.TAG + ".Notification";
@@ -200,12 +194,7 @@
// override notification copy if hybrid notification enabled
if (mEstimate != null) {
title = mContext.getString(R.string.battery_low_title_hybrid);
- contentText = mContext.getString(
- mEstimate.isBasedOnUsage
- ? R.string.battery_low_percent_format_hybrid
- : R.string.battery_low_percent_format_hybrid_short,
- percentage,
- getTimeRemainingFormatted());
+ contentText = getHybridContentString(percentage);
}
final Notification.Builder nb =
@@ -239,21 +228,12 @@
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
}
- @VisibleForTesting
- String getTimeRemainingFormatted() {
- final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
- MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW);
-
- final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS;
- final long hours = TimeUnit.MILLISECONDS.toHours(
- mEstimate.estimateMillis - remainder);
- // round down to the nearest 15 min for now to not appear overly precise
- final long minutes = TimeUnit.MILLISECONDS.toMinutes(
- remainder - (remainder % TimeUnit.MINUTES.toMillis(15)));
- final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR);
- final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE);
-
- return frmt.formatMeasures(hoursMeasure, minutesMeasure);
+ private String getHybridContentString(String percentage) {
+ return PowerUtil.getBatteryRemainingStringFormatted(
+ mContext,
+ mEstimate.estimateMillis,
+ percentage,
+ mEstimate.isBasedOnUsage);
}
private PendingIntent pendingBroadcast(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index ea2a432..ac86c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -53,7 +53,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
@@ -72,10 +71,11 @@
private final Configuration mLastConfiguration = new Configuration();
private int mBatteryLevel = 100;
private long mTimeRemaining = Long.MAX_VALUE;
- private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
private int mPlugType = 0;
private int mInvalidCharger = 0;
private EnhancedEstimates mEnhancedEstimates;
+ private boolean mLowWarningShownThisChargeCycle;
+ private boolean mSevereWarningShownThisChargeCycle;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -88,6 +88,8 @@
private long mNextLogTime;
private IThermalService mThermalService;
+ @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+
// by using the same instance (method references are not guaranteed to be the same object
// We create a method reference here so that we are guaranteed that we can remove a callback
// each time they are created).
@@ -218,6 +220,12 @@
final boolean plugged = mPlugType != 0;
final boolean oldPlugged = oldPlugType != 0;
+ // if we are now unplugged but we were previously plugged in we should allow the
+ // time based trigger again.
+ if (!plugged && plugged != oldPlugged) {
+ mLowWarningShownThisChargeCycle = false;
+ mSevereWarningShownThisChargeCycle = false;
+ }
int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
int bucket = findBatteryLevelBucket(mBatteryLevel);
@@ -268,7 +276,6 @@
boolean isPowerSaver = mPowerManager.isPowerSaveMode();
// only play SFX when the dialog comes up or the bucket changes
final boolean playSound = bucket != oldBucket || oldPlugged;
- long oldTimeRemaining = mTimeRemaining;
if (mEnhancedEstimates.isHybridNotificationEnabled()) {
final Estimate estimate = mEnhancedEstimates.getEstimate();
// Turbo is not always booted once SysUI is running so we have ot make sure we actually
@@ -281,10 +288,18 @@
}
}
- if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining,
- mTimeRemaining,
- isPowerSaver, mBatteryStatus)) {
+ if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
+ mTimeRemaining, isPowerSaver, mBatteryStatus)) {
mWarnings.showLowBatteryWarning(playSound);
+
+ // mark if we've already shown a warning this cycle. This will prevent the time based
+ // trigger from spamming users since the time remaining can vary based on current
+ // device usage.
+ if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()) {
+ mSevereWarningShownThisChargeCycle = true;
+ } else {
+ mLowWarningShownThisChargeCycle = true;
+ }
} else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
isPowerSaver)) {
mWarnings.dismissLowBatteryWarning();
@@ -295,22 +310,14 @@
@VisibleForTesting
boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
- int bucket, long oldTimeRemaining, long timeRemaining,
- boolean isPowerSaver, int mBatteryStatus) {
+ int bucket, long timeRemaining, boolean isPowerSaver, int mBatteryStatus) {
return !plugged
&& !isPowerSaver
&& (((bucket < oldBucket || oldPlugged) && bucket < 0)
- || (mEnhancedEstimates.isHybridNotificationEnabled()
- && timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
- && isHourLess(oldTimeRemaining, timeRemaining)))
+ || isTimeBasedTrigger(timeRemaining))
&& mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
}
- private boolean isHourLess(long oldTimeRemaining, long timeRemaining) {
- final long dif = oldTimeRemaining - timeRemaining;
- return dif >= TimeUnit.HOURS.toMillis(1);
- }
-
@VisibleForTesting
boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
long timeRemaining, boolean isPowerSaver) {
@@ -323,6 +330,23 @@
|| hybridWouldDismiss));
}
+ private boolean isTimeBasedTrigger(long timeRemaining) {
+ if (!mEnhancedEstimates.isHybridNotificationEnabled()) {
+ return false;
+ }
+
+ // Only show the time based warning once per charge cycle
+ final boolean canShowWarning = timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
+ && !mLowWarningShownThisChargeCycle;
+
+ // Only show the severe time based warning once per charge cycle
+ final boolean canShowSevereWarning =
+ timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
+ && !mSevereWarningShownThisChargeCycle;
+
+ return canShowWarning || canShowSevereWarning;
+ }
+
private void initTemperatureWarning() {
ContentResolver resolver = mContext.getContentResolver();
Resources resources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
index 9cda75c..7b1509d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -14,8 +14,11 @@
package com.android.systemui.qs;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
import android.support.v4.widget.NestedScrollView;
+import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -23,6 +26,8 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.qs.touch.OverScroll;
+import com.android.systemui.qs.touch.SwipeDetector;
/**
* Quick setting scroll view containing the brightness slider and the QS tiles.
@@ -35,6 +40,9 @@
private final int mTouchSlop;
private final int mFooterHeight;
private int mLastMotionY;
+ private final SwipeDetector mSwipeDetector;
+ private final OverScrollHelper mOverScrollHelper;
+ private float mContentTranslationY;
public QSScrollLayout(Context context, View... children) {
super(context);
@@ -49,6 +57,35 @@
linearLayout.addView(view);
}
addView(linearLayout);
+ setOverScrollMode(OVER_SCROLL_NEVER);
+ mOverScrollHelper = new OverScrollHelper();
+ mSwipeDetector = new SwipeDetector(context, mOverScrollHelper, SwipeDetector.VERTICAL);
+ mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!canScrollVertically(1) && !canScrollVertically(-1)) {
+ return false;
+ }
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!canScrollVertically(1) && !canScrollVertically(-1)) {
+ return false;
+ }
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ canvas.translate(0, mContentTranslationY);
+ super.dispatchDraw(canvas);
+ canvas.translate(0, -mContentTranslationY);
}
public boolean shouldIntercept(MotionEvent ev) {
@@ -81,4 +118,81 @@
parent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
+
+ private void setContentTranslationY(float contentTranslationY) {
+ mContentTranslationY = contentTranslationY;
+ invalidate();
+ }
+
+ private static final Property<QSScrollLayout, Float> CONTENT_TRANS_Y =
+ new Property<QSScrollLayout, Float>(Float.class, "qsScrollLayoutContentTransY") {
+ @Override
+ public Float get(QSScrollLayout qsScrollLayout) {
+ return qsScrollLayout.mContentTranslationY;
+ }
+
+ @Override
+ public void set(QSScrollLayout qsScrollLayout, Float y) {
+ qsScrollLayout.setContentTranslationY(y);
+ }
+ };
+
+ private class OverScrollHelper implements SwipeDetector.Listener {
+ private boolean mIsInOverScroll;
+
+ // We use this value to calculate the actual amount the user has overscrolled.
+ private float mFirstDisplacement = 0;
+
+ @Override
+ public void onDragStart(boolean start) {}
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ // Only overscroll if the user is scrolling down when they're already at the bottom
+ // or scrolling up when they're already at the top.
+ boolean wasInOverScroll = mIsInOverScroll;
+ mIsInOverScroll = (!canScrollVertically(1) && displacement < 0) ||
+ (!canScrollVertically(-1) && displacement > 0);
+
+ if (wasInOverScroll && !mIsInOverScroll) {
+ // Exit overscroll. This can happen when the user is in overscroll and then
+ // scrolls the opposite way. Note that this causes the reset translation animation
+ // to run while the user is dragging, which feels a bit unnatural.
+ reset();
+ } else if (mIsInOverScroll) {
+ if (Float.compare(mFirstDisplacement, 0) == 0) {
+ // Because users can scroll before entering overscroll, we need to
+ // subtract the amount where the user was not in overscroll.
+ mFirstDisplacement = displacement;
+ }
+ float overscrollY = displacement - mFirstDisplacement;
+ setContentTranslationY(getDampedOverScroll(overscrollY));
+ }
+
+ return mIsInOverScroll;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ reset();
+ }
+
+ private void reset() {
+ if (Float.compare(mContentTranslationY, 0) != 0) {
+ ObjectAnimator.ofFloat(QSScrollLayout.this, CONTENT_TRANS_Y, 0)
+ .setDuration(100)
+ .start();
+ }
+ mIsInOverScroll = false;
+ mFirstDisplacement = 0;
+ }
+
+ public boolean isInOverScroll() {
+ return mIsInOverScroll;
+ }
+
+ private float getDampedOverScroll(float y) {
+ return OverScroll.dampedScroll(y, getHeight());
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3b9e7bc..65135ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -104,6 +104,11 @@
setMeasuredDimension(width, height);
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
private static int exactly(int size) {
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index ea6e174..3597929 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.provider.Settings;
import android.service.quicksettings.Tile;
+import android.support.annotation.StringRes;
import android.widget.Switch;
import com.android.internal.app.ColorDisplayController;
@@ -30,6 +31,7 @@
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class NightDisplayTile extends QSTileImpl<BooleanState>
@@ -39,7 +41,9 @@
* Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
* nearest hour and add on the AM/PM indicator.
*/
- private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h a";
+ private static final String HOUR_MINUTE_DATE_TIME_PATTERN = "h a";
+ private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h:m a";
+
private ColorDisplayController mController;
private boolean mIsListening;
@@ -110,17 +114,26 @@
case ColorDisplayController.AUTO_MODE_CUSTOM:
// User-specified time, approximated to the nearest hour.
- return isNightLightActivated
- ? mContext.getString(
- R.string.quick_settings_night_secondary_label_until,
- mController.getCustomEndTime().format(
- DateTimeFormatter.ofPattern(
- APPROXIMATE_HOUR_DATE_TIME_PATTERN)))
- : mContext.getString(
- R.string.quick_settings_night_secondary_label_on_at,
- mController.getCustomStartTime().format(
- DateTimeFormatter.ofPattern(
- APPROXIMATE_HOUR_DATE_TIME_PATTERN)));
+ final @StringRes int toggleTimeStringRes;
+ final LocalTime toggleTime;
+ final DateTimeFormatter toggleTimeFormat;
+
+ if (isNightLightActivated) {
+ toggleTime = mController.getCustomEndTime();
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_until;
+ } else {
+ toggleTime = mController.getCustomStartTime();
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at;
+ }
+
+ // Choose between just showing the hour or also showing the minutes (based on the
+ // user-selected toggle time). This helps reduce how much space the label takes.
+ toggleTimeFormat = DateTimeFormatter.ofPattern(
+ toggleTime.getMinute() == 0
+ ? HOUR_MINUTE_DATE_TIME_PATTERN
+ : APPROXIMATE_HOUR_DATE_TIME_PATTERN);
+
+ return mContext.getString(toggleTimeStringRes, toggleTime.format(toggleTimeFormat));
default:
// No secondary label when auto mode is disabled.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
new file mode 100644
index 0000000..0464886
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.touch;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+ /**
+ * This curve determines how the effect of scrolling over the limits of the page diminishes
+ * as the user pulls further and further from the bounds
+ *
+ * @param f The percentage of how much the user has overscrolled.
+ * @return A transformed percentage based on the influence curve.
+ */
+ private static float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ /**
+ * @param amount The original amount overscrolled.
+ * @param max The maximum amount that the View can overscroll.
+ * @return The dampened overscroll amount.
+ */
+ public static int dampedScroll(float amount, int max) {
+ if (Float.compare(amount, 0) == 0) return 0;
+
+ float f = amount / max;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
new file mode 100644
index 0000000..2522052
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/SwipeDetector.java
+ */
+public class SwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SwipeDetector";
+
+ private int mScrollConditions;
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ private static final float ANIMATION_DURATION = 1200;
+
+ protected int mActivePointerId = INVALID_POINTER_ID;
+
+ /**
+ * The minimum release velocity in pixels per millisecond that triggers fling..
+ */
+ public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+
+ /**
+ * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+ * Cutoff frequency is set at 10 Hz.
+ */
+ public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+ /* Scroll state, this is set to true during dragging and animation. */
+ private ScrollState mState = ScrollState.IDLE;
+
+ enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ public static abstract class Direction {
+
+ abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint);
+
+ /**
+ * Distance in pixels a touch can wander before we think the user is scrolling.
+ */
+ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+ }
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getY(pointerIndex) - refPoint.y;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getX(pointerIndex) - downPos.x);
+ }
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getX(pointerIndex) - refPoint.x;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getY(pointerIndex) - downPos.y);
+ }
+ };
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final Direction mDir;
+
+ private final float mTouchSlop;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private long mCurrentMillis;
+
+ private float mVelocity;
+ private float mLastDisplacement;
+ private float mDisplacement;
+
+ private float mSubtractDisplacement;
+ private boolean mIgnoreSlopWhenSettling;
+
+ public interface Listener {
+ void onDragStart(boolean start);
+
+ boolean onDrag(float displacement, float velocity);
+
+ void onDragEnd(float velocity, boolean fling);
+ }
+
+ public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+ this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+ }
+
+ @VisibleForTesting
+ protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
+ mTouchSlop = touchSlope;
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollConditions = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+ // reject cases where the angle or slop condition is not met.
+ if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+ > Math.abs(mDisplacement)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) ||
+ ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement = 0;
+ mDisplacement = 0;
+ mVelocity = 0;
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
+ computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
+ ev.getEventTime());
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging();
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ private boolean reportDragStart(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ return true;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement = 0;
+ }
+ if (mDisplacement > 0) {
+ mSubtractDisplacement = mTouchSlop;
+ } else {
+ mSubtractDisplacement = -mTouchSlop;
+ }
+ }
+
+ private boolean reportDragging() {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+
+ mLastDisplacement = mDisplacement;
+ return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+ }
+ return true;
+ }
+
+ private void reportDragEnd() {
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+ mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+
+ }
+
+ /**
+ * Computes the damped velocity.
+ */
+ public float computeVelocity(float delta, long currentMillis) {
+ long previousMillis = mCurrentMillis;
+ mCurrentMillis = currentMillis;
+
+ float deltaTimeMillis = mCurrentMillis - previousMillis;
+ float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+ if (Math.abs(mVelocity) < 0.001f) {
+ mVelocity = velocity;
+ } else {
+ float alpha = computeDampeningFactor(deltaTimeMillis);
+ mVelocity = interpolate(mVelocity, velocity, alpha);
+ }
+ return mVelocity;
+ }
+
+ /**
+ * Returns a time-dependent dampening factor using delta time.
+ */
+ private static float computeDampeningFactor(float deltaTime) {
+ return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+ }
+
+ /**
+ * Returns the linear interpolation between two values
+ */
+ private static float interpolate(float from, float to, float alpha) {
+ return (1.0f - alpha) * from + alpha * to;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
rename to packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index b2472bf..0ff8b08 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -16,13 +16,11 @@
package com.android.systemui.recents;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import android.annotation.TargetApi;
import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -31,6 +29,8 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -41,6 +41,7 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
@@ -50,9 +51,9 @@
* Shows onboarding for the new recents interaction in P (codenamed quickstep).
*/
@TargetApi(Build.VERSION_CODES.P)
-public class SwipeUpOnboarding {
+public class RecentsOnboarding {
- private static final String TAG = "SwipeUpOnboarding";
+ private static final String TAG = "RecentsOnboarding";
private static final boolean RESET_PREFS_FOR_DEBUG = false;
private static final long SHOW_DELAY_MS = 500;
private static final long SHOW_HIDE_DURATION_MS = 300;
@@ -61,6 +62,7 @@
private final Context mContext;
private final WindowManager mWindowManager;
+ private final OverviewProxyService mOverviewProxyService;
private final View mLayout;
private final TextView mTextView;
private final ImageView mDismissView;
@@ -113,11 +115,12 @@
}
};
- public SwipeUpOnboarding(Context context) {
+ public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) {
mContext = context;
+ mOverviewProxyService = overviewProxyService;
final Resources res = context.getResources();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null);
+ mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null);
mTextView = mLayout.findViewById(R.id.onboarding_text);
mDismissView = mLayout.findViewById(R.id.dismiss);
mDarkBackgroundColor = res.getColor(android.R.color.background_dark);
@@ -135,25 +138,25 @@
mDismissView.setOnClickListener(v -> hide(true));
if (RESET_PREFS_FOR_DEBUG) {
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
}
}
public void onConnectedToLauncher() {
- boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
- if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) {
+ boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) {
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
mTaskListenerRegistered = true;
}
}
public void onRecentsAnimationStarted() {
- boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
- if (!alreadyLearnedSwipeUpForRecents) {
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true);
+ boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ if (!alreadySeenRecentsOnboarding) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true);
onDisconnectedFromLauncher();
}
}
@@ -173,6 +176,12 @@
}
public void show() {
+ CharSequence onboardingText = mOverviewProxyService.getOnboardingText();
+ if (TextUtils.isEmpty(onboardingText)) {
+ Log.w(TAG, "Unable to get onboarding text");
+ return;
+ }
+ mTextView.setText(onboardingText);
// Only show in portrait.
int orientation = mContext.getResources().getConfiguration().orientation;
if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -239,7 +248,7 @@
flags,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
- lp.setTitle("SwipeUpOnboarding");
+ lp.setTitle("RecentsOnboarding");
lp.gravity = Gravity.BOTTOM;
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 11bdf6b3..fa177f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -156,7 +156,7 @@
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
- default void showChargingAnimation(int batteryLevel) { }
+ default void showWirelessChargingAnimation(int batteryLevel) { }
default void onRotationProposal(int rotation, boolean isValid) { }
@@ -497,7 +497,7 @@
}
@Override
- public void showChargingAnimation(int batteryLevel) {
+ public void showWirelessChargingAnimation(int batteryLevel) {
mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION);
mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0)
.sendToTarget();
@@ -784,7 +784,7 @@
break;
case MSG_SHOW_CHARGING_ANIMATION:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showChargingAnimation(msg.arg1);
+ mCallbacks.get(i).showWirelessChargingAnimation(msg.arg1);
}
break;
case MSG_SHOW_PINNING_TOAST_ENTER_EXIT:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index d6beb7f..ab89a52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -249,6 +249,9 @@
(GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
gradientDrawable.setXfermode(
running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
+ // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
+ // spot during animation anyways.
+ gradientDrawable.setAntiAlias(!running);
}
if (!mExpandAnimationRunning) {
setDrawableAlpha(mDrawableAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3ebeb4d..3dfb913 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -379,7 +379,7 @@
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by removing all heads-
// up notifications.
- mHeadsUpManager.removeAllHeadsUpEntries();
+ mHeadsUpManager.releaseAllImmediately();
super.animateExpandNotificationsPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 8227b77..d3a325d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,6 +21,8 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.internal.widget.MessagingMessage;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -117,13 +119,15 @@
@Override
protected boolean transformScale(TransformState otherState) {
- return true;
+ return sameAs(otherState);
}
@Override
public void recycle() {
super.recycle();
- sInstancePool.release(this);
+ if (getClass() == ImageTransformState.class) {
+ sInstancePool.release(this);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
new file mode 100644
index 0000000..b97995d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+import android.view.View;
+
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+/**
+ * A transform state of a image view.
+*/
+public class MessagingImageTransformState extends ImageTransformState {
+ private static Pools.SimplePool<MessagingImageTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width;
+ private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height;
+ private MessagingImageMessage mImageMessage;
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ mImageMessage = (MessagingImageMessage) view;
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (super.sameAs(otherState)) {
+ return true;
+ }
+ if (otherState instanceof MessagingImageTransformState) {
+ MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState;
+ return mImageMessage.sameAs(otherMessage.mImageMessage);
+ }
+ return false;
+ }
+
+ public static MessagingImageTransformState obtain() {
+ MessagingImageTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingImageTransformState();
+ }
+
+ @Override
+ protected boolean transformScale(TransformState otherState) {
+ return false;
+ }
+
+ @Override
+ protected void transformViewFrom(TransformState otherState, int transformationFlags,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ super.transformViewFrom(otherState, transformationFlags, customTransformation,
+ transformationAmount);
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
+ transformationAmount);
+ if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) {
+ MessagingImageMessage otherMessage
+ = ((MessagingImageTransformState) otherState).mImageMessage;
+ if (transformationAmount == 0.0f) {
+ setStartActualWidth(otherMessage.getActualWidth());
+ setStartActualHeight(otherMessage.getActualHeight());
+ }
+ float startActualWidth = getStartActualWidth();
+ mImageMessage.setActualWidth(
+ (int) NotificationUtils.interpolate(startActualWidth,
+ mImageMessage.getStaticWidth(),
+ interpolatedValue));
+ float startActualHeight = getStartActualHeight();
+ mImageMessage.setActualHeight(
+ (int) NotificationUtils.interpolate(startActualHeight,
+ mImageMessage.getHeight(),
+ interpolatedValue));
+ }
+ }
+
+ public int getStartActualWidth() {
+ Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH);
+ return tag == null ? -1 : (int) tag;
+ }
+
+ public void setStartActualWidth(int actualWidth) {
+ mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth);
+ }
+
+ public int getStartActualHeight() {
+ Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT);
+ return tag == null ? -1 : (int) tag;
+ }
+
+ public void setStartActualHeight(int actualWidth) {
+ mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth);
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ if (getClass() == MessagingImageTransformState.class) {
+ sInstancePool.release(this);
+ }
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ mImageMessage.setActualWidth(mImageMessage.getStaticWidth());
+ mImageMessage.setActualHeight(mImageMessage.getHeight());
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mImageMessage = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index 113118a..314a31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -22,6 +22,7 @@
import android.view.ViewGroup;
import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.internal.widget.MessagingMessage;
@@ -30,6 +31,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* A transform state of the action list
@@ -156,6 +158,7 @@
}
appear(ownGroup.getAvatar(), transformationAmount);
appear(ownGroup.getSenderView(), transformationAmount);
+ appear(ownGroup.getIsolatedMessage(), transformationAmount);
setClippingDeactivated(ownGroup.getSenderView(), true);
setClippingDeactivated(ownGroup.getAvatar(), true);
}
@@ -187,12 +190,13 @@
}
disappear(ownGroup.getAvatar(), transformationAmount);
disappear(ownGroup.getSenderView(), transformationAmount);
+ disappear(ownGroup.getIsolatedMessage(), transformationAmount);
setClippingDeactivated(ownGroup.getSenderView(), true);
setClippingDeactivated(ownGroup.getAvatar(), true);
}
private void appear(View child, float transformationAmount) {
- if (child.getVisibility() == View.GONE) {
+ if (child == null || child.getVisibility() == View.GONE) {
return;
}
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -201,7 +205,7 @@
}
private void disappear(View child, float transformationAmount) {
- if (child.getVisibility() == View.GONE) {
+ if (child == null || child.getVisibility() == View.GONE) {
return;
}
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -224,22 +228,24 @@
private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
float transformationAmount, boolean to) {
+ boolean useLinearTransformation =
+ otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating();
transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(),
- true /* sameAsAny */);
+ true /* sameAsAny */, useLinearTransformation);
transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
- true /* sameAsAny */);
- MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
- MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+ true /* sameAsAny */, useLinearTransformation);
+ List<MessagingMessage> ownMessages = ownGroup.getMessages();
+ List<MessagingMessage> otherMessages = otherGroup.getMessages();
float previousTranslation = 0;
- for (int i = 0; i < ownMessages.getChildCount(); i++) {
- View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+ for (int i = 0; i < ownMessages.size(); i++) {
+ View child = ownMessages.get(ownMessages.size() - 1 - i).getView();
if (isGone(child)) {
continue;
}
- int otherIndex = otherMessages.getChildCount() - 1 - i;
+ int otherIndex = otherMessages.size() - 1 - i;
View otherChild = null;
if (otherIndex >= 0) {
- otherChild = otherMessages.getChildAt(otherIndex);
+ otherChild = otherMessages.get(otherIndex).getView();
if (isGone(otherChild)) {
otherChild = null;
}
@@ -252,7 +258,12 @@
transformationAmount = 1.0f - transformationAmount;
}
}
- transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+ transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
+ useLinearTransformation);
+ if (transformationAmount == 0.0f
+ && otherGroup.getIsolatedMessage() == otherChild) {
+ ownGroup.setTransformingImages(true);
+ }
if (otherChild == null) {
child.setTranslationY(previousTranslation);
setClippingDeactivated(child, true);
@@ -264,12 +275,13 @@
previousTranslation = child.getTranslationY();
}
}
+ ownGroup.updateClipRect();
}
private void transformView(float transformationAmount, boolean to, View ownView,
- View otherView, boolean sameAsAny) {
+ View otherView, boolean sameAsAny, boolean useLinearTransformation) {
TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
- if (!mTransformInfo.isAnimating()) {
+ if (useLinearTransformation) {
ownState.setDefaultInterpolator(Interpolators.LINEAR);
}
ownState.setIsSameAsAnyView(sameAsAny);
@@ -339,11 +351,15 @@
if (!isGone(ownGroup)) {
MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
for (int j = 0; j < ownMessages.getChildCount(); j++) {
- MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+ View child = ownMessages.getChildAt(j);
setVisible(child, visible, force);
}
setVisible(ownGroup.getAvatar(), visible, force);
setVisible(ownGroup.getSenderView(), visible, force);
+ MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ setVisible(isolatedMessage, visible, force);
+ }
}
}
}
@@ -375,11 +391,17 @@
}
resetTransformedView(ownGroup.getAvatar());
resetTransformedView(ownGroup.getSenderView());
+ MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ resetTransformedView(isolatedMessage);
+ }
setClippingDeactivated(ownGroup.getAvatar(), false);
setClippingDeactivated(ownGroup.getSenderView(), false);
ownGroup.setTranslationY(0);
ownGroup.getMessageContainer().setTranslationY(0);
}
+ ownGroup.setTransformingImages(false);
+ ownGroup.updateClipRect();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 918b6ed..fc8ceb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,7 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
@@ -80,7 +81,7 @@
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
- private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
+ protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
@@ -131,7 +132,7 @@
transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
- private void transformViewFrom(TransformState otherState, int transformationFlags,
+ protected void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
@@ -449,6 +450,11 @@
result.initFrom(view, transformInfo);
return result;
}
+ if (view instanceof MessagingImageMessage) {
+ MessagingImageTransformState result = MessagingImageTransformState.obtain();
+ result.initFrom(view, transformInfo);
+ return result;
+ }
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view, transformInfo);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
new file mode 100644
index 0000000..aba5cdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.util.ArraySet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Stack;
+
+/**
+ * A implementation of HeadsUpManager for phone and car.
+ */
+public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
+ ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+ OnHeadsUpChangedListener {
+ private static final String TAG = "HeadsUpManagerPhone";
+ private static final boolean DEBUG = false;
+
+ private final View mStatusBarWindowView;
+ private final int mStatusBarHeight;
+ private final NotificationGroupManager mGroupManager;
+ private final StatusBar mBar;
+ private final VisualStabilityManager mVisualStabilityManager;
+
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+ = new ArraySet<>();
+ private boolean mIsExpanded;
+ private int[] mTmpTwoArray = new int[2];
+ private boolean mHeadsUpGoingAway;
+ private boolean mWaitingOnCollapseWhenGoingAway;
+ private boolean mIsObserving;
+ private int mStatusBarState;
+
+ private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
+ private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntryPhone acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntryPhone();
+ }
+
+ @Override
+ public boolean release(@NonNull HeadsUpEntryPhone instance) {
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor:
+
+ public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView,
+ @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar,
+ @NonNull VisualStabilityManager visualStabilityManager) {
+ super(context);
+
+ mStatusBarWindowView = statusBarWindowView;
+ mGroupManager = groupManager;
+ mBar = bar;
+ mVisualStabilityManager = visualStabilityManager;
+
+ Resources resources = mContext.getResources();
+ mStatusBarHeight = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+
+ addListener(new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+ if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged");
+ updateTouchableRegionListener();
+ }
+ });
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Public methods:
+
+ /**
+ * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
+ * that a user might have consciously clicked on it.
+ *
+ * @param key the key of the touched notification
+ * @return whether the touch is invalid and should be discarded
+ */
+ public boolean shouldSwallowClick(@NonNull String key) {
+ HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+ return entry != null && mClock.currentTimeMillis() < entry.postTime;
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+
+ /**
+ * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+ * from the list even after a Heads Up Notification is gone.
+ */
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ /**
+ * Notify that the status bar panel gets expanded or collapsed.
+ *
+ * @param isExpanded True to notify expanded, false to notify collapsed.
+ */
+ public void setIsPanelExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ // make sure our state is sane
+ mWaitingOnCollapseWhenGoingAway = false;
+ mHeadsUpGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ }
+
+ /**
+ * Set the current state of the statusbar.
+ */
+ public void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ }
+
+ /**
+ * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+ * animating out. This is used to keep the touchable regions in a sane state.
+ */
+ public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+ if (headsUpGoingAway != mHeadsUpGoingAway) {
+ mHeadsUpGoingAway = headsUpGoingAway;
+ if (!headsUpGoingAway) {
+ waitForStatusBarLayout();
+ }
+ updateTouchableRegionListener();
+ }
+ }
+
+ /**
+ * Notifies that a remote input textbox in notification gets active or inactive.
+ * @param entry The entry of the target notification.
+ * @param remoteInputActive True to notify active, False to notify inactive.
+ */
+ public void setRemoteInputActive(
+ @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
+ if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+ headsUpEntry.remoteInputActive = remoteInputActive;
+ if (remoteInputActive) {
+ headsUpEntry.removeAutoRemovalCallbacks();
+ } else {
+ headsUpEntry.updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void removeMinimumDisplayTimeForTesting() {
+ mMinimumDisplayTime = 0;
+ mHeadsUpNotificationDecay = 0;
+ mTouchAcceptanceDelay = 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager public methods overrides:
+
+ @Override
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
+ @Override
+ public void snooze() {
+ super.snooze();
+ mReleaseOnExpandFinish = true;
+ }
+
+ /**
+ * React to the removal of the notification in the heads up.
+ *
+ * @return true if the notification was removed and false if it still needs to be kept around
+ * for a bit since it wasn't shown long enough
+ */
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
+ return super.removeNotification(key, ignoreEarliestRemovalTime);
+ } else {
+ HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key);
+ entry.removeAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ public void addSwipedOutNotification(@NonNull String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Dumpable overrides:
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpManagerPhone state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ViewTreeObserver.OnComputeInternalInsetsListener overrides:
+
+ /**
+ * Overridden from TreeObserver.
+ */
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (mIsExpanded || mBar.isBouncerShowing()) {
+ // The touchable region is always the full area when expanded
+ return;
+ }
+ if (hasPinnedHeadsUp()) {
+ ExpandableNotificationRow topEntry = getTopEntry().row;
+ if (topEntry.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
+ if (groupSummary != null) {
+ topEntry = groupSummary;
+ }
+ }
+ topEntry.getLocationOnScreen(mTmpTwoArray);
+ int minX = mTmpTwoArray[0];
+ int maxX = mTmpTwoArray[0] + topEntry.getWidth();
+ int maxY = topEntry.getIntrinsicHeight();
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, 0, maxX, maxY);
+ } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VisualStabilityManager.Callback overrides:
+
+ @Override
+ public void onReorderingAllowed() {
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+ for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ mEntriesToRemoveWhenReorderingAllowed.clear();
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager utility (protected) methods overrides:
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return mEntryPool.acquire();
+ }
+
+ @Override
+ protected void releaseHeadsUpEntry(HeadsUpEntry entry) {
+ entry.reset();
+ mEntryPool.release((HeadsUpEntryPhone) entry);
+ }
+
+ @Override
+ protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
+ || super.shouldHeadsUpBecomePinned(entry);
+ }
+
+ @Override
+ protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpInternal(fd, pw, args);
+ pw.print(" mStatusBarState="); pw.println(mStatusBarState);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Private utility methods:
+
+ @Nullable
+ private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
+ return (HeadsUpEntryPhone) getHeadsUpEntry(key);
+ }
+
+ @Nullable
+ private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
+ return (HeadsUpEntryPhone) getTopHeadsUpEntry();
+ }
+
+ private boolean wasShownLongEnough(@NonNull String key) {
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
+ HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
+ return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough();
+ }
+
+ /**
+ * We need to wait on the whole panel to collapse, before we can remove the touchable region
+ * listener.
+ */
+ private void waitForStatusBarLayout() {
+ mWaitingOnCollapseWhenGoingAway = true;
+ mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
+ mStatusBarWindowView.removeOnLayoutChangeListener(this);
+ mWaitingOnCollapseWhenGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ });
+ }
+
+ private void updateTouchableRegionListener() {
+ boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+ || mWaitingOnCollapseWhenGoingAway;
+ if (shouldObserve == mIsObserving) {
+ return;
+ }
+ if (shouldObserve) {
+ mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mStatusBarWindowView.requestLayout();
+ } else {
+ mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+ mIsObserving = shouldObserve;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpEntryPhone:
+
+ protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
+ public void setEntry(@NonNull final NotificationData.Entry entry) {
+ Runnable removeHeadsUpRunnable = () -> {
+ if (!mVisualStabilityManager.isReorderingAllowed()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ mVisualStabilityManager.addReorderingAllowedCallback(
+ HeadsUpManagerPhone.this);
+ } else if (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ };
+
+ super.setEntry(entry, removeHeadsUpRunnable);
+ }
+
+ public boolean wasShownLongEnough() {
+ return earliestRemovaltime < mClock.currentTimeMillis();
+ }
+
+ @Override
+ public void updateEntry(boolean updatePostTime) {
+ super.updateEntry(updatePostTime);
+
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(entry);
+ }
+ }
+
+ @Override
+ public void expanded(boolean expanded) {
+ if (this.expanded == expanded) {
+ return;
+ }
+
+ this.expanded = expanded;
+ if (expanded) {
+ removeAutoRemovalCallbacks();
+ } else {
+ updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index c85571c..2bfdefe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,7 +23,7 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
/**
@@ -31,7 +31,7 @@
*/
public class HeadsUpTouchHelper implements Gefingerpoken {
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private NotificationStackScrollLayout mStackScroller;
private int mTrackingPointer;
private float mTouchSlop;
@@ -43,7 +43,7 @@
private NotificationPanelView mPanel;
private ExpandableNotificationRow mPickedChild;
- public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+ public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
NotificationStackScrollLayout stackScroller,
NotificationPanelView notificationPanelView) {
mHeadsUpManager = headsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 0d36efd..8d14db7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -16,37 +16,37 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
+import static com.android.systemui.OverviewProxyService.TAG_OPS;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
-
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.Dependency;
import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.tuner.TunerService;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_TOP;
-import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
-import static com.android.systemui.OverviewProxyService.TAG_OPS;
-
/**
* Class to detect gestures on the navigation bar.
*/
@@ -84,8 +84,16 @@
private int mTouchDownY;
private boolean mDownOnRecents;
private VelocityTracker mVelocityTracker;
- private OverviewProxyService mOverviewEventSender = Dependency.get(OverviewProxyService.class);
+ private OverviewProxyService mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
+ @Override
+ public void onRecentsAnimationStarted() {
+ mRecentsAnimationStarted = true;
+ mQuickScrubController.cancelQuickSwitch();
+ }
+ };
+ private boolean mRecentsAnimationStarted;
private boolean mDockWindowEnabled;
private boolean mDockWindowTouchSlopExceeded;
private int mDragMode;
@@ -97,10 +105,12 @@
mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
mQuickScrubController = new QuickScrubController(context);
Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
+ mOverviewProxyService.addCallback(mOverviewProxyListener);
}
public void destroy() {
Dependency.get(TunerService.class).removeTunable(this);
+ mOverviewProxyService.removeCallback(mOverviewProxyListener);
}
public void setComponents(RecentsComponent recentsComponent, Divider divider,
@@ -117,7 +127,7 @@
}
private boolean proxyMotionEvents(MotionEvent event) {
- final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ final IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
if (overviewProxy != null) {
mNavigationBarView.requestUnbufferedDispatch(event);
event.transform(mTransformGlobalMatrix);
@@ -146,6 +156,19 @@
mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
+ mRecentsAnimationStarted = false;
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // If the overview proxy service has not started the recents animation then clean up
+ // after it to ensure that the nav bar buttons still work
+ if (mOverviewProxyService.getProxy() != null && !mRecentsAnimationStarted) {
+ try {
+ ActivityManager.getService().cancelRecentsAnimation();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not cancel recents animation", e);
+ }
+ }
break;
}
}
@@ -154,11 +177,25 @@
proxyMotionEvents(event);
return false;
}
- return (mDockWindowEnabled && interceptDockWindowEvent(event));
+ return (mDockWindowEnabled && interceptDockWindowEvent(event)) || mRecentsAnimationStarted;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ // The same down event was just sent on intercept and therefore can be ignored here
+ boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
+ && mOverviewProxyService.getProxy() != null;
+ boolean result = mStatusBar.isPresenterFullyCollapsed()
+ && (mQuickScrubController.onTouchEvent(event)
+ || ignoreProxyDownEvent
+ || proxyMotionEvents(event));
+ if (mDockWindowEnabled) {
+ result |= handleDockWindowEvent(event);
+ }
+ return result || mRecentsAnimationStarted;
}
public void onDraw(Canvas canvas) {
- if (mOverviewEventSender.getProxy() != null) {
+ if (mOverviewProxyService.getProxy() != null) {
mQuickScrubController.onDraw(canvas);
}
}
@@ -307,20 +344,6 @@
return DRAG_MODE_RECENTS;
}
- public boolean onTouchEvent(MotionEvent event) {
- // The same down event was just sent on intercept and therefore can be ignored here
- boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
- && mOverviewEventSender.getProxy() != null;
- boolean result = mStatusBar.isPresenterFullyCollapsed()
- && (mQuickScrubController.onTouchEvent(event)
- || ignoreProxyDownEvent
- || proxyMotionEvents(event));
- if (mDockWindowEnabled) {
- result |= handleDockWindowEvent(event);
- }
- return result;
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index c37dd55..af0afbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -50,14 +50,13 @@
import com.android.systemui.Dependency;
import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.OverviewProxyService;
-import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.recents.SwipeUpOnboarding;
+import com.android.systemui.recents.RecentsOnboarding;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
@@ -104,7 +103,6 @@
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
private final OverviewProxyService mOverviewProxyService;
- private boolean mRecentsAnimationStarted;
// workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -126,7 +124,7 @@
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsComponent mRecentsComponent;
private Divider mDivider;
- private SwipeUpOnboarding mSwipeUpOnboarding;
+ private RecentsOnboarding mRecentsOnboarding;
private NotificationPanelView mPanelView;
private class NavTransitionListener implements TransitionListener {
@@ -236,7 +234,7 @@
new ButtonDispatcher(R.id.rotate_suggestion));
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
- mSwipeUpOnboarding = new SwipeUpOnboarding(context);
+ mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
}
public BarTransitions getBarTransitions() {
@@ -264,9 +262,8 @@
}
public void setRecentsAnimationStarted(boolean started) {
- mRecentsAnimationStarted = started;
- if (mSwipeUpOnboarding != null) {
- mSwipeUpOnboarding.onRecentsAnimationStarted();
+ if (mRecentsOnboarding != null) {
+ mRecentsOnboarding.onRecentsAnimationStarted();
}
}
@@ -277,30 +274,16 @@
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return mGestureHelper.onInterceptTouchEvent(event);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureHelper.onTouchEvent(event)) {
return true;
}
- return mRecentsAnimationStarted || super.onTouchEvent(event);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- mRecentsAnimationStarted = false;
- } else if (action == MotionEvent.ACTION_UP) {
- // If the overview proxy service has not started the recents animation then clean up
- // after it to ensure that the nav bar buttons still work
- if (mOverviewProxyService.getProxy() != null && !mRecentsAnimationStarted) {
- try {
- ActivityManager.getService().cancelRecentsAnimation();
- } catch (RemoteException e) {
- Log.e(TAG, "Could not cancel recents animation");
- }
- }
- }
- return mGestureHelper.onInterceptTouchEvent(event) || mRecentsAnimationStarted;
+ return super.onTouchEvent(event);
}
public void abortCurrentGesture() {
@@ -674,8 +657,8 @@
if (mGestureHelper != null) {
mGestureHelper.onDarkIntensityChange(intensity);
}
- if (mSwipeUpOnboarding != null) {
- mSwipeUpOnboarding.setContentDarkIntensity(intensity);
+ if (mRecentsOnboarding != null) {
+ mRecentsOnboarding.setContentDarkIntensity(intensity);
}
}
@@ -793,7 +776,7 @@
updateTaskSwitchHelper();
updateIcons(getContext(), mConfiguration, newConfig);
updateRecentsIcon();
- mSwipeUpOnboarding.onConfigurationChanged(newConfig);
+ mRecentsOnboarding.onConfigurationChanged(newConfig);
if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
|| mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
@@ -897,9 +880,9 @@
private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
if (connectedToOverviewProxy) {
- mSwipeUpOnboarding.onConnectedToLauncher();
+ mRecentsOnboarding.onConnectedToLauncher();
} else {
- mSwipeUpOnboarding.onDisconnectedFromLauncher();
+ mRecentsOnboarding.onDisconnectedFromLauncher();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index cd2e77a..52d005c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -68,14 +68,12 @@
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.List;
-import java.util.Collection;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener,
@@ -1571,7 +1569,7 @@
private void updatePanelExpanded() {
boolean isExpanded = !isFullyCollapsed();
if (mPanelExpanded != isExpanded) {
- mHeadsUpManager.setIsExpanded(isExpanded);
+ mHeadsUpManager.setIsPanelExpanded(isExpanded);
mStatusBar.setPanelExpanded(isExpanded);
mPanelExpanded = isExpanded;
}
@@ -2338,7 +2336,7 @@
}
@Override
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
super.setHeadsUpManager(headsUpManager);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
this);
@@ -2630,8 +2628,8 @@
}
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
- mKeyguardStatusView.setPulsing(pulsing != null);
+ public void setPulsing(boolean pulsing) {
+ mKeyguardStatusView.setPulsing(pulsing);
positionClockAndNotifications();
mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
+ mKeyguardStatusView.getClockBottom());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2b7e474..6daabed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -50,7 +50,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -75,7 +75,7 @@
}
protected StatusBar mStatusBar;
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private float mPeekHeight;
private float mHintDistance;
@@ -1252,7 +1252,7 @@
*/
protected abstract int getClearAllHeight();
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
index 001a1a2..dc0835e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -63,7 +63,7 @@
private static final String TAG = "QuickScrubController";
private static final int QUICK_SWITCH_FLING_VELOCITY = 0;
private static final int ANIM_DURATION_MS = 200;
- private static final long LONG_PRESS_DELAY_MS = 150;
+ private static final long LONG_PRESS_DELAY_MS = 225;
/**
* For quick step, set a damping value to allow the button to stick closer its origin position
@@ -76,6 +76,7 @@
private boolean mDraggingActive;
private boolean mQuickScrubActive;
+ private boolean mAllowQuickSwitch;
private float mDownOffset;
private float mTranslation;
private int mTouchDownX;
@@ -95,7 +96,6 @@
private final Paint mTrackPaint = new Paint();
private final int mScrollTouchSlop;
private final OverviewProxyService mOverviewEventSender;
- private final Display mDisplay;
private final int mTrackThickness;
private final int mTrackPadding;
private final ValueAnimator mTrackAnimator;
@@ -137,7 +137,8 @@
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
- if (!isQuickScrubEnabled() || mQuickScrubActive) {
+ if (!isQuickScrubEnabled() || mQuickScrubActive || !mAllowQuickSwitch ||
+ !mHomeButtonRect.contains(mTouchDownX, mTouchDownY)) {
return false;
}
float velocityX = mIsRTL ? -velX : velX;
@@ -158,16 +159,15 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to send start of quick switch.", e);
}
+ return true;
}
- return true;
+ return false;
}
};
public QuickScrubController(Context context) {
mContext = context;
mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mDisplay = ((WindowManager) context.getSystemService(
- Context.WINDOW_SERVICE)).getDefaultDisplay();
mOverviewEventSender = Dependency.get(OverviewProxyService.class);
mGestureDetector = new GestureDetector(mContext, mGestureListener);
mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness);
@@ -189,6 +189,10 @@
mNavigationBarView = navigationBarView;
}
+ /**
+ * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying
+ * the event to the overview service.
+ */
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
@@ -197,7 +201,26 @@
homeButton.setDelayTouchFeedback(false);
return false;
}
- mGestureDetector.onTouchEvent(event);
+
+ return handleTouchEvent(event);
+ }
+
+ /**
+ * @return true if we want to handle touch events for quick scrub/switch and prevent proxying
+ * the event to the overview service.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return handleTouchEvent(event);
+ }
+
+ private boolean handleTouchEvent(MotionEvent event) {
+ final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+ if (mGestureDetector.onTouchEvent(event)) {
+ // If the fling has been handled on UP, then skip proxying the UP
+ return true;
+ }
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
@@ -212,6 +235,7 @@
homeButton.setDelayTouchFeedback(false);
mTouchDownX = mTouchDownY = -1;
}
+ mAllowQuickSwitch = true;
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -240,8 +264,9 @@
offset = pos - mTrackRect.left;
trackSize = mTrackRect.width();
}
- // Do not start scrubbing when dragging in the perpendicular direction
- if (!mDraggingActive && exceededPerpendicularTouchSlop) {
+ // Do not start scrubbing when dragging in the perpendicular direction if we
+ // haven't already started quickscrub
+ if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) {
mHandler.removeCallbacksAndMessages(null);
return false;
}
@@ -341,14 +366,6 @@
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- endQuickScrub();
- }
- return false;
- }
-
- @Override
public void setBarState(boolean isVertical, boolean isRTL) {
mIsVertical = isVertical;
mIsRTL = isRTL;
@@ -403,6 +420,11 @@
mDraggingActive = false;
}
+ public void cancelQuickSwitch() {
+ mAllowQuickSwitch = false;
+ mHandler.removeCallbacks(mLongPressRunnable);
+ }
+
private int getDimensionPixelSize(Context context, @DimenRes int resId) {
return context.getResources().getDimensionPixelSize(resId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 44e0c25..1438763 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -30,6 +30,7 @@
import android.os.Trace;
import android.util.Log;
import android.util.MathUtils;
+import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -111,7 +112,6 @@
protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
static final int TAG_KEY_ANIM = R.id.scrim;
- static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
@@ -167,6 +167,10 @@
private Callback mCallback;
private boolean mWallpaperSupportsAmbientMode;
+ // Scrim blanking callbacks
+ private Choreographer.FrameCallback mPendingFrameCallback;
+ private Runnable mBlankingTransitionRunnable;
+
private final WakeLock mWakeLock;
private boolean mWakeLockHeld;
@@ -248,6 +252,16 @@
mCurrentInFrontAlpha = state.getFrontAlpha();
mCurrentBehindAlpha = state.getBehindAlpha();
+ // Cancel blanking transitions that were pending before we requested a new state
+ if (mPendingFrameCallback != null) {
+ Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback);
+ mPendingFrameCallback = null;
+ }
+ if (getHandler().hasCallbacks(mBlankingTransitionRunnable)) {
+ getHandler().removeCallbacks(mBlankingTransitionRunnable);
+ mBlankingTransitionRunnable = null;
+ }
+
// Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
// to do the same when you're just showing the brightness mirror.
mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
@@ -276,13 +290,18 @@
mWallpaperVisibilityTimedOut = false;
}
- if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
- scheduleUpdate();
- } else {
+ if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
// In case the user isn't unlocked, make sure to delay a bit because the system is hosed
// with too many things at this case, in order to not skip the initial frames.
mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+ } else if (!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) {
+ // Execute first frame immediately when display was completely off.
+ // Scheduling a frame isn't enough because the system may aggressively enter doze,
+ // delaying callbacks or never triggering them until the power button is pressed.
+ onPreDraw();
+ } else {
+ scheduleUpdate();
}
}
@@ -687,11 +706,12 @@
}
}
- final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
- if (mBlankScreen || blankingInProgress) {
- if (!blankingInProgress) {
- blankDisplay();
- }
+ if (mPendingFrameCallback != null) {
+ // Display is off and we're waiting.
+ return;
+ } else if (mBlankScreen) {
+ // Need to blank the display before continuing.
+ blankDisplay();
return;
} else if (!mScreenBlankingCallbackCalled) {
// Not blanking the screen. Letting the callback know that we're ready
@@ -745,45 +765,37 @@
}
private void blankDisplay() {
- final float initialAlpha = mScrimInFront.getViewAlpha();
- final int initialTint = mScrimInFront.getTint();
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- anim.addUpdateListener(animation -> {
- final float amount = (float) animation.getAnimatedValue();
- float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
- int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
- updateScrimColor(mScrimInFront, animAlpha, animTint);
- dispatchScrimsVisible();
- });
- anim.setInterpolator(getInterpolator());
- anim.setDuration(mDozeParameters.getPulseInDuration());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCallback != null) {
- mCallback.onDisplayBlanked();
- mScreenBlankingCallbackCalled = true;
- }
- Runnable blankingCallback = () -> {
- mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
- mBlankScreen = false;
- // Try again.
- updateScrims();
- };
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- getHandler().postDelayed(blankingCallback, 100);
-
+ // Notify callback that the screen is completely black and we're
+ // ready to change the display power mode
+ mPendingFrameCallback = frameTimeNanos -> {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
}
- });
- anim.start();
- mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
- // Finish animation if we're already at its final state
- if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
- anim.end();
- }
+ mBlankingTransitionRunnable = () -> {
+ mBlankingTransitionRunnable = null;
+ mPendingFrameCallback = null;
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ if (DEBUG) {
+ Log.d(TAG, "Waiting for the screen to turn on...");
+ }
+ getHandler().postDelayed(mBlankingTransitionRunnable, 500);
+ };
+ doOnTheNextFrame(mPendingFrameCallback);
+ }
+
+ @VisibleForTesting
+ protected void doOnTheNextFrame(Choreographer.FrameCallback callback) {
+ Choreographer.getInstance().postFrameCallback(callback);
}
@VisibleForTesting
@@ -888,6 +900,20 @@
}
}
+ /**
+ * Interrupts blanking transitions once the display notifies that it's already on.
+ */
+ public void onScreenTurnedOn() {
+ final Handler handler = getHandler();
+ if (handler.hasCallbacks(mBlankingTransitionRunnable)) {
+ if (DEBUG) {
+ Log.d(TAG, "Shorter blanking because screen turned on. All good.");
+ }
+ handler.removeCallbacks(mBlankingTransitionRunnable);
+ mBlankingTransitionRunnable.run();
+ }
+ }
+
public interface Callback {
default void onStart() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 314d6aa..381e4af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -48,7 +48,6 @@
// set our scrim to black in this frame to avoid flickering and
// fade it out afterwards.
mBlankScreen = true;
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
}
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
@@ -86,9 +85,6 @@
AOD(3) {
@Override
public void prepare(ScrimState previousState) {
- if (previousState == ScrimState.PULSING && !mCanControlScreenOff) {
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
- }
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
final boolean wasPulsing = previousState == ScrimState.PULSING;
mBlankScreen = wasPulsing && !mCanControlScreenOff;
@@ -115,9 +111,6 @@
&& !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
mCurrentBehindTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
- if (mDisplayRequiresBlanking) {
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
- }
}
},
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1bf719a..458518c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -140,7 +140,6 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
@@ -152,6 +151,7 @@
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -181,6 +181,7 @@
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -208,6 +209,7 @@
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -219,6 +221,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -809,15 +812,14 @@
.commit();
mIconController = Dependency.get(StatusBarIconController.class);
- mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
- mHeadsUpManager.setBar(this);
+ mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this,
+ mVisualStabilityManager);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
- mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
@@ -1348,7 +1350,8 @@
@Override
public void onPerformRemoveNotification(StatusBarNotification n) {
- if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+ if (mStackScroller.hasPulsingNotifications() &&
+ !mHeadsUpManager.hasHeadsUpNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
mDozeScrimController.pulseOutNow();
@@ -2097,9 +2100,8 @@
}
public void maybeEscalateHeadsUp() {
- Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
- for (HeadsUpManager.HeadsUpEntry entry : entries) {
- final StatusBarNotification sbn = entry.entry.notification;
+ mHeadsUpManager.getAllEntries().forEach(entry -> {
+ final StatusBarNotification sbn = entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG) {
@@ -2109,11 +2111,11 @@
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
notification.fullScreenIntent.send();
- entry.entry.notifyFullScreenIntentLaunched();
+ entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
}
- }
+ });
mHeadsUpManager.releaseAllImmediately();
}
@@ -2459,14 +2461,25 @@
}
@Override
- public void showChargingAnimation(int batteryLevel) {
- if (mDozing) {
- // ambient
- } else if (mKeyguardManager.isKeyguardLocked()) {
- // lockscreen
- } else {
+ public void showWirelessChargingAnimation(int batteryLevel) {
+ if (mDozing || mKeyguardManager.isKeyguardLocked()) {
+ // on ambient or lockscreen, hide notification panel
WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
- batteryLevel).show();
+ batteryLevel, new WirelessChargingAnimation.Callback() {
+ @Override
+ public void onAnimationStarting() {
+ CrossFadeHelper.fadeOut(mNotificationPanel, 1);
+ }
+
+ @Override
+ public void onAnimationEnded() {
+ CrossFadeHelper.fadeIn(mNotificationPanel);
+ }
+ }).show();
+ } else {
+ // workspace
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ batteryLevel, null).show();
}
}
@@ -3882,6 +3895,7 @@
private void instantCollapseNotificationPanel() {
mNotificationPanel.instantCollapse();
+ runPostCollapseRunnables();
}
@Override
@@ -4393,6 +4407,7 @@
@Override
public void onScreenTurnedOn() {
+ mScrimController.onScreenTurnedOn();
}
@Override
@@ -4658,24 +4673,22 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
- mHeadsUpManager.getAllEntries();
- if (!pulsingEntries.isEmpty()) {
+ if (mHeadsUpManager.hasHeadsUpNotifications()) {
// Only pulse the stack scroller if there's actually something to show.
// Otherwise just show the always-on screen.
- setPulsing(pulsingEntries);
+ setPulsing(true);
}
}
@Override
public void onPulseFinished() {
callback.onPulseFinished();
- setPulsing(null);
+ setPulsing(false);
}
- private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+ private void setPulsing(boolean pulsing) {
mNotificationPanel.setPulsing(pulsing);
- mVisualStabilityManager.setPulsing(pulsing != null);
+ mVisualStabilityManager.setPulsing(pulsing);
mIgnoreTouchWhilePulsing = false;
}
}, reason);
@@ -4823,7 +4836,7 @@
// for heads up notifications
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private AboveShelfObserver mAboveShelfObserver;
@@ -4926,7 +4939,7 @@
// Release the HUN notification to the shade.
if (isPresenterFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
}
//
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
@@ -5045,6 +5058,8 @@
} else if (!isPresenterFullyCollapsed()) {
instantCollapseNotificationPanel();
visibilityChanged(false);
+ } else {
+ runPostCollapseRunnables();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 53dfb24..040d7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,119 +16,69 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.os.SystemClock;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.v4.util.ArraySet;
import android.util.ArrayMap;
+import android.provider.Settings;
import android.util.Log;
-import android.util.Pools;
-import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Iterator;
+import java.util.stream.Stream;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Stack;
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
- VisualStabilityManager.Callback {
+public class HeadsUpManager {
private static final String TAG = "HeadsUpManager";
private static final boolean DEBUG = false;
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
- private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
- private final int mHeadsUpNotificationDecay;
- private final int mMinimumDisplayTime;
+ protected final Clock mClock = new Clock();
+ protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ protected final Handler mHandler = new Handler(Looper.getMainLooper());
- private final int mTouchAcceptanceDelay;
+ protected final Context mContext;
+
+ protected int mHeadsUpNotificationDecay;
+ protected int mMinimumDisplayTime;
+ protected int mTouchAcceptanceDelay;
+ protected int mSnoozeLengthMs;
+ protected boolean mHasPinnedNotification;
+ protected int mUser;
+
+ private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
private final ArrayMap<String, Long> mSnoozedPackages;
- private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
- private final int mDefaultSnoozeLengthMs;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
- private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
-
- @Override
- public HeadsUpEntry acquire() {
- if (!mPoolObjects.isEmpty()) {
- return mPoolObjects.pop();
- }
- return new HeadsUpEntry();
- }
-
- @Override
- public boolean release(HeadsUpEntry instance) {
- instance.reset();
- mPoolObjects.push(instance);
- return true;
- }
- };
-
- private final View mStatusBarWindowView;
- private final int mStatusBarHeight;
- private final Context mContext;
- private final NotificationGroupManager mGroupManager;
- private StatusBar mBar;
- private int mSnoozeLengthMs;
- private ContentObserver mSettingsObserver;
- private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
- private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private int mUser;
- private Clock mClock;
- private boolean mReleaseOnExpandFinish;
- private boolean mTrackingHeadsUp;
- private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
- = new ArraySet<>();
- private boolean mIsExpanded;
- private boolean mHasPinnedNotification;
- private int[] mTmpTwoArray = new int[2];
- private boolean mHeadsUpGoingAway;
- private boolean mWaitingOnCollapseWhenGoingAway;
- private boolean mIsObserving;
- private boolean mRemoteInputActive;
- private float mExpandedHeight;
- private VisualStabilityManager mVisualStabilityManager;
- private int mStatusBarState;
-
- public HeadsUpManager(final Context context, View statusBarWindowView,
- NotificationGroupManager groupManager) {
+ public HeadsUpManager(@NonNull final Context context) {
mContext = context;
- Resources resources = mContext.getResources();
- mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
- mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
- mClock = new Clock();
+ mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
+ mSnoozedPackages = new ArrayMap<>();
+ int defaultSnoozeLengthMs =
+ resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
- SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
- mSettingsObserver = new ContentObserver(mHandler) {
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+ ContentObserver settingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
final int packageSnoozeLengthMs = Settings.Global.getInt(
@@ -141,48 +91,27 @@
};
context.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
- mSettingsObserver);
- mStatusBarWindowView = statusBarWindowView;
- mGroupManager = groupManager;
- mStatusBarHeight = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
+ settingsObserver);
}
- private void updateTouchableRegionListener() {
- boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
- || mWaitingOnCollapseWhenGoingAway;
- if (shouldObserve == mIsObserving) {
- return;
- }
- if (shouldObserve) {
- mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mStatusBarWindowView.requestLayout();
- } else {
- mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- }
- mIsObserving = shouldObserve;
- }
-
- public void setBar(StatusBar bar) {
- mBar = bar;
- }
-
- public void addListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Adds an OnHeadUpChangedListener to observe events.
+ */
+ public void addListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.add(listener);
}
- public void removeListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Removes the OnHeadUpChangedListener from the observer list.
+ */
+ public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.remove(listener);
}
- public StatusBar getBar() {
- return mBar;
- }
-
/**
* Called when posting a new notification to the heads up.
*/
- public void showNotification(NotificationData.Entry headsUp) {
+ public void showNotification(@NonNull NotificationData.Entry headsUp) {
if (DEBUG) Log.v(TAG, "showNotification");
addHeadsUpEntry(headsUp);
updateNotification(headsUp, true);
@@ -192,7 +121,7 @@
/**
* Called when updating or posting a notification to the heads up.
*/
- public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
if (DEBUG) Log.v(TAG, "updateNotification");
headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -204,14 +133,13 @@
// with the groupmanager
return;
}
- headsUpEntry.updateEntry();
+ headsUpEntry.updateEntry(true /* updatePostTime */);
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
}
}
- private void addHeadsUpEntry(NotificationData.Entry entry) {
- HeadsUpEntry headsUpEntry = mEntryPool.acquire();
-
+ private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
// This will also add the entry to the sortedList
headsUpEntry.setEntry(entry);
mHeadsUpEntries.put(entry.key, headsUpEntry);
@@ -223,16 +151,17 @@
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
- private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
- return mStatusBarState != StatusBarState.KEYGUARD
- && !mIsExpanded || hasFullScreenIntent(entry);
+ protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+ return hasFullScreenIntent(entry);
}
- private boolean hasFullScreenIntent(NotificationData.Entry entry) {
+ protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
return entry.notification.getNotification().fullScreenIntent != null;
}
- private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
+ protected void setEntryPinned(
+ @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
+ if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
ExpandableNotificationRow row = headsUpEntry.entry.row;
if (row.isPinned() != isPinned) {
row.setPinned(isPinned);
@@ -247,33 +176,35 @@
}
}
- private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) {
HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ onHeadsUpEntryRemoved(remove);
+ }
+
+ protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) {
+ NotificationData.Entry entry = remove.entry;
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
entry.row.setHeadsUp(false);
setEntryPinned(remove, false /* isPinned */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
- mEntryPool.release(remove);
+ releaseHeadsUpEntry(remove);
}
- public void removeAllHeadsUpEntries() {
- for (String key : mHeadsUpEntries.keySet()) {
- removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
- }
- }
-
- private void updatePinnedMode() {
+ protected void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
}
+ if (DEBUG) {
+ Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
+ hasPinnedNotification);
+ }
mHasPinnedNotification = hasPinnedNotification;
if (mHasPinnedNotification) {
MetricsLogger.count(mContext, "note_peek", 1);
}
- updateTouchableRegionListener();
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
}
@@ -285,47 +216,36 @@
* @return true if the notification was removed and false if it still needs to be kept around
* for a bit since it wasn't shown long enough
*/
- public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
- if (DEBUG) Log.v(TAG, "remove");
- if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
- releaseImmediately(key);
- return true;
- } else {
- getHeadsUpEntry(key).removeAsSoonAsPossible();
- return false;
- }
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (DEBUG) Log.v(TAG, "removeNotification");
+ releaseImmediately(key);
+ return true;
}
- private boolean wasShownLongEnough(String key) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- HeadsUpEntry topEntry = getTopEntry();
- if (mSwipedOutKeys.contains(key)) {
- // We always instantly dismiss views being manually swiped out.
- mSwipedOutKeys.remove(key);
- return true;
- }
- if (headsUpEntry != topEntry) {
- return true;
- }
- return headsUpEntry.wasShownLongEnough();
- }
-
- public boolean isHeadsUp(String key) {
+ /**
+ * Returns if the given notification is in the Heads Up Notification list or not.
+ */
+ public boolean isHeadsUp(@NonNull String key) {
return mHeadsUpEntries.containsKey(key);
}
/**
- * Push any current Heads Up notification down into the shade.
+ * Pushes any current Heads Up notification down into the shade.
*/
public void releaseAllImmediately() {
if (DEBUG) Log.v(TAG, "releaseAllImmediately");
- ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
- for (String key : keys) {
- releaseImmediately(key);
+ Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
+ while (iterator.hasNext()) {
+ HeadsUpEntry entry = iterator.next();
+ iterator.remove();
+ onHeadsUpEntryRemoved(entry);
}
}
- public void releaseImmediately(String key) {
+ /**
+ * Pushes the given Heads Up notification down into the shade.
+ */
+ public void releaseImmediately(@NonNull String key) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
if (headsUpEntry == null) {
return;
@@ -334,11 +254,14 @@
removeHeadsUpEntry(shadeEntry);
}
- public boolean isSnoozed(String packageName) {
+ /**
+ * Returns if the given notification is snoozed or not.
+ */
+ public boolean isSnoozed(@NonNull String packageName) {
final String key = snoozeKey(packageName, mUser);
Long snoozedUntil = mSnoozedPackages.get(key);
if (snoozedUntil != null) {
- if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (snoozedUntil > mClock.currentTimeMillis()) {
if (DEBUG) Log.v(TAG, key + " snoozed");
return true;
}
@@ -347,39 +270,71 @@
return false;
}
+ /**
+ * Snoozes all current Heads Up Notifications.
+ */
public void snooze() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
String packageName = entry.entry.notification.getPackageName();
mSnoozedPackages.put(snoozeKey(packageName, mUser),
- SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+ mClock.currentTimeMillis() + mSnoozeLengthMs);
}
- mReleaseOnExpandFinish = true;
}
- private static String snoozeKey(String packageName, int user) {
+ @NonNull
+ private static String snoozeKey(@NonNull String packageName, int user) {
return user + "," + packageName;
}
- private HeadsUpEntry getHeadsUpEntry(String key) {
+ @Nullable
+ protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
return mHeadsUpEntries.get(key);
}
- public NotificationData.Entry getEntry(String key) {
- return mHeadsUpEntries.get(key).entry;
+ /**
+ * Returns the entry of given Heads Up Notification.
+ *
+ * @param key Key of heads up notification
+ */
+ @Nullable
+ public NotificationData.Entry getEntry(@NonNull String key) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ return entry != null ? entry.entry : null;
}
- public Collection<HeadsUpEntry> getAllEntries() {
- return mHeadsUpEntries.values();
+ /**
+ * Returns the stream of all current Heads Up Notifications.
+ */
+ @NonNull
+ public Stream<NotificationData.Entry> getAllEntries() {
+ return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
}
- public HeadsUpEntry getTopEntry() {
+ /**
+ * Returns the top Heads Up Notification, which appeares to show at first.
+ */
+ @Nullable
+ public NotificationData.Entry getTopEntry() {
+ HeadsUpEntry topEntry = getTopHeadsUpEntry();
+ return (topEntry != null) ? topEntry.entry : null;
+ }
+
+ /**
+ * Returns if any heads up notification is available or not.
+ */
+ public boolean hasHeadsUpNotifications() {
+ return !mHeadsUpEntries.isEmpty();
+ }
+
+ @Nullable
+ protected HeadsUpEntry getTopHeadsUpEntry() {
if (mHeadsUpEntries.isEmpty()) {
return null;
}
HeadsUpEntry topEntry = null;
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
- if (topEntry == null || entry.compareTo(topEntry) == -1) {
+ if (topEntry == null || entry.compareTo(topEntry) < 0) {
topEntry = entry;
}
}
@@ -387,56 +342,22 @@
}
/**
- * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
- * that a user might have consciously clicked on it.
- *
- * @param key the key of the touched notification
- * @return whether the touch is invalid and should be discarded
+ * Sets the current user.
*/
- public boolean shouldSwallowClick(String key) {
- HeadsUpEntry entry = mHeadsUpEntries.get(key);
- if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
- return true;
- }
- return false;
- }
-
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (mIsExpanded || mBar.isBouncerShowing()) {
- // The touchable region is always the full area when expanded
- return;
- }
- if (mHasPinnedNotification) {
- ExpandableNotificationRow topEntry = getTopEntry().entry.row;
- if (topEntry.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
- if (groupSummary != null) {
- topEntry = groupSummary;
- }
- }
- topEntry.getLocationOnScreen(mTmpTwoArray);
- int minX = mTmpTwoArray[0];
- int maxX = mTmpTwoArray[0] + topEntry.getWidth();
- int maxY = topEntry.getIntrinsicHeight();
-
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(minX, 0, maxX, maxY);
- } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
- }
- }
-
public void setUser(int user) {
mUser = user;
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("HeadsUpManager state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ protected void dumpInternal(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
- pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" now="); pw.println(mClock.currentTimeMillis());
pw.print(" mUser="); pw.println(mUser);
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
pw.print(" HeadsUpEntry="); pw.println(entry.entry);
@@ -449,6 +370,9 @@
}
}
+ /**
+ * Returns if there are any pinned Heads Up Notifications or not.
+ */
public boolean hasPinnedHeadsUp() {
return mHasPinnedNotification;
}
@@ -464,14 +388,8 @@
}
/**
- * Notifies that a notification was swiped out and will be removed.
- *
- * @param key the notification key
+ * Unpins all pinned Heads Up Notifications.
*/
- public void addSwipedOutNotification(String key) {
- mSwipedOutKeys.add(key);
- }
-
public void unpinAll() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
@@ -481,60 +399,13 @@
}
}
- public void onExpandingFinished() {
- if (mReleaseOnExpandFinish) {
- releaseAllImmediately();
- mReleaseOnExpandFinish = false;
- } else {
- for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- }
- mEntriesToRemoveAfterExpand.clear();
- }
-
- public void setTrackingHeadsUp(boolean trackingHeadsUp) {
- mTrackingHeadsUp = trackingHeadsUp;
- }
-
- public boolean isTrackingHeadsUp() {
- return mTrackingHeadsUp;
- }
-
- public void setIsExpanded(boolean isExpanded) {
- if (isExpanded != mIsExpanded) {
- mIsExpanded = isExpanded;
- if (isExpanded) {
- // make sure our state is sane
- mWaitingOnCollapseWhenGoingAway = false;
- mHeadsUpGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- }
-
/**
- * @return the height of the top heads up notification when pinned. This is different from the
- * intrinsic height, which also includes whether the notification is system expanded and
- * is mainly used when dragging down from a heads up notification.
+ * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+ * well.
*/
- public int getTopHeadsUpPinnedHeight() {
- HeadsUpEntry topEntry = getTopEntry();
- if (topEntry == null || topEntry.entry == null) {
- return 0;
- }
- ExpandableNotificationRow row = topEntry.entry.row;
- if (row.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(row.getStatusBarNotification());
- if (groupSummary != null) {
- row = groupSummary;
- }
- }
- return row.getPinnedHeadsUpHeight();
+ public boolean isTrackingHeadsUp() {
+ // Might be implemented in subclass.
+ return false;
}
/**
@@ -543,7 +414,7 @@
* @return -1 if the first argument should be ranked higher than the second, 1 if the second
* one should be ranked higher and 0 if they are equal.
*/
- public int compare(NotificationData.Entry a, NotificationData.Entry b) {
+ public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
if (aEntry == null || bEntry == null) {
@@ -553,147 +424,62 @@
}
/**
- * Set that we are exiting the headsUp pinned mode, but some notifications might still be
- * animating out. This is used to keep the touchable regions in a sane state.
- */
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
- if (headsUpGoingAway != mHeadsUpGoingAway) {
- mHeadsUpGoingAway = headsUpGoingAway;
- if (!headsUpGoingAway) {
- waitForStatusBarLayout();
- }
- updateTouchableRegionListener();
- }
- }
-
- /**
- * We need to wait on the whole panel to collapse, before we can remove the touchable region
- * listener.
- */
- private void waitForStatusBarLayout() {
- mWaitingOnCollapseWhenGoingAway = true;
- mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
- mStatusBarWindowView.removeOnLayoutChangeListener(this);
- mWaitingOnCollapseWhenGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- });
- }
-
- public static void setIsClickedNotification(View child, boolean clicked) {
- child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
- }
-
- public static boolean isClickedHeadsUpNotification(View child) {
- Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
- return clicked != null && clicked;
- }
-
- public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
- headsUpEntry.remoteInputActive = remoteInputActive;
- if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
- }
- }
-
- /**
* Set an entry to be expanded and therefore stick in the heads up area if it's pinned
* until it's collapsed again.
*/
- public void setExpanded(NotificationData.Entry entry, boolean expanded) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) {
- headsUpEntry.expanded = expanded;
- if (expanded) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
+ public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+ HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+ if (headsUpEntry != null && entry.row.isPinned()) {
+ headsUpEntry.expanded(expanded);
}
}
- @Override
- public void onReorderingAllowed() {
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- mEntriesToRemoveWhenReorderingAllowed.clear();
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ @NonNull
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return new HeadsUpEntry();
}
- public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
- mVisualStabilityManager = visualStabilityManager;
- }
-
- public void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
+ protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
+ entry.reset();
}
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
- public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
- public NotificationData.Entry entry;
+ protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ @Nullable public NotificationData.Entry entry;
public long postTime;
- public long earliestRemovaltime;
- private Runnable mRemoveHeadsUpRunnable;
public boolean remoteInputActive;
+ public long earliestRemovaltime;
public boolean expanded;
- public void setEntry(final NotificationData.Entry entry) {
+ @Nullable private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(@Nullable final NotificationData.Entry entry) {
+ setEntry(entry, null);
+ }
+
+ public void setEntry(@Nullable final NotificationData.Entry entry,
+ @Nullable Runnable removeHeadsUpRunnable) {
this.entry = entry;
+ this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
// The actual post time will be just after the heads-up really slided in
postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
- mRemoveHeadsUpRunnable = new Runnable() {
- @Override
- public void run() {
- if (!mVisualStabilityManager.isReorderingAllowed()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
- } else if (!mTrackingHeadsUp) {
- removeHeadsUpEntry(entry);
- } else {
- mEntriesToRemoveAfterExpand.add(entry);
- }
- }
- };
- updateEntry();
- }
-
- public void updateEntry() {
- updateEntry(true);
+ updateEntry(true /* updatePostTime */);
}
public void updateEntry(boolean updatePostTime) {
+ if (DEBUG) Log.v(TAG, "updateEntry");
+
long currentTime = mClock.currentTimeMillis();
earliestRemovaltime = currentTime + mMinimumDisplayTime;
if (updatePostTime) {
postTime = Math.max(postTime, currentTime);
}
removeAutoRemovalCallbacks();
- if (mEntriesToRemoveAfterExpand.contains(entry)) {
- mEntriesToRemoveAfterExpand.remove(entry);
- }
- if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(entry);
- }
+
if (!isSticky()) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
@@ -707,7 +493,7 @@
}
@Override
- public int compareTo(HeadsUpEntry o) {
+ public int compareTo(@NonNull HeadsUpEntry o) {
boolean isPinned = entry.row.isPinned();
boolean otherPinned = o.entry.row.isPinned();
if (isPinned && !otherPinned) {
@@ -734,26 +520,29 @@
: -1;
}
- public void removeAutoRemovalCallbacks() {
- mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
- }
-
- public boolean wasShownLongEnough() {
- return earliestRemovaltime < mClock.currentTimeMillis();
- }
-
- public void removeAsSoonAsPossible() {
- removeAutoRemovalCallbacks();
- mHandler.postDelayed(mRemoveHeadsUpRunnable,
- earliestRemovaltime - mClock.currentTimeMillis());
+ public void expanded(boolean expanded) {
+ this.expanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks();
entry = null;
- mRemoveHeadsUpRunnable = null;
expanded = false;
remoteInputActive = false;
+ removeAutoRemovalCallbacks();
+ mRemoveHeadsUpRunnable = null;
+ }
+
+ public void removeAutoRemovalCallbacks() {
+ if (mRemoveHeadsUpRunnable != null)
+ mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+ }
+
+ public void removeAsSoonAsPossible() {
+ if (mRemoveHeadsUpRunnable != null) {
+ removeAutoRemovalCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable,
+ earliestRemovaltime - mClock.currentTimeMillis());
+ }
}
}
@@ -762,5 +551,4 @@
return SystemClock.elapsedRealtime();
}
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
new file mode 100644
index 0000000..1e3c123c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A class of utility static methods for heads up notifications.
+ */
+public final class HeadsUpUtil {
+ private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+
+ /**
+ * Set the given view as clicked or not-clicked.
+ * @param view The view to be set the flag to.
+ * @param clicked True to set as clicked. False to not-clicked.
+ */
+ public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
+ view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+ }
+
+ /**
+ * Check if the given view has the flag of "clicked notification"
+ * @param view The view to be checked.
+ * @return True if the view has clicked. False othrewise.
+ */
+ public static boolean isClickedHeadsUpNotification(View view) {
+ Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
+ return clicked != null && clicked;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 077c6c3..ea449c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -28,6 +28,8 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
@@ -45,6 +47,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
@@ -58,12 +61,16 @@
private long mDownTime;
private int mCode;
private int mTouchSlop;
+ private int mTouchDownX;
+ private int mTouchDownY;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
private boolean mGestureAborted;
private boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
+ private final OverviewProxyService mOverviewProxyService;
+ private final Vibrator mVibrator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final Runnable mCheckLongPress = new Runnable() {
@@ -110,6 +117,8 @@
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mRipple = new KeyButtonRipple(context, this);
+ mVibrator = mContext.getSystemService(Vibrator.class);
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
setBackground(mRipple);
}
@@ -189,6 +198,7 @@
}
public boolean onTouchEvent(MotionEvent ev) {
+ final boolean isProxyConnected = mOverviewProxyService.getProxy() != null;
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
@@ -203,23 +213,34 @@
mDownTime = SystemClock.uptimeMillis();
mLongClicked = false;
setPressed(true);
+ mTouchDownX = (int) ev.getX();
+ mTouchDownY = (int) ev.getY();
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
} else {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- playSoundEffect(SoundEffectConstants.CLICK);
+ if (isProxyConnected) {
+ // Provide small vibration for quick step or immediate down feedback
+ AsyncTask.execute(() ->
+ mVibrator.vibrate(VibrationEffect
+ .get(VibrationEffect.EFFECT_TICK, false)));
+ } else {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
removeCallbacks(mCheckLongPress);
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
x = (int)ev.getX();
y = (int)ev.getY();
- setPressed(x >= -mTouchSlop
- && x < getWidth() + mTouchSlop
- && y >= -mTouchSlop
- && y < getHeight() + mTouchSlop);
+ boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop;
+ boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop;
+ if (exceededTouchSlopX || exceededTouchSlopY) {
+ setPressed(false);
+ removeCallbacks(mCheckLongPress);
+ }
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
@@ -231,9 +252,14 @@
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
- // Always send a release ourselves because it doesn't seem to be sent elsewhere
- // and it feels weird to sometimes get a release haptic and other times not.
- if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ if (isProxyConnected) {
+ if (doIt) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ } else if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ // Always send a release ourselves because it doesn't seem to be sent elsewhere
+ // and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
}
if (mCode != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 424858a..d7a810e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -64,7 +64,7 @@
private boolean mPanelTracking;
private boolean mExpansionChanging;
private boolean mPanelFullWidth;
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mUnlockHintRunning;
private boolean mQsCustomizerShowing;
private int mIntrinsicPadding;
@@ -315,23 +315,18 @@
}
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) {
+ public void setPulsing(boolean hasPulsing) {
mPulsing = hasPulsing;
}
public boolean isPulsing(NotificationData.Entry entry) {
- if (mPulsing == null) {
+ if (!mPulsing || mHeadsUpManager == null) {
return false;
}
- for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
- if (e.entry == entry) {
- return true;
- }
- }
- return false;
+ return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
}
public boolean isPanelTracking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c114a6f..1b55a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -92,10 +92,11 @@
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import android.support.v4.graphics.ColorUtils;
@@ -288,7 +289,7 @@
private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
= new HashSet<>();
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private boolean mTrackingHeadsUp;
private ScrimController mScrimController;
private boolean mForceNoOverlappingRendering;
@@ -358,7 +359,7 @@
}
};
private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mDrawBackgroundAsSrc;
private boolean mFadingOut;
private boolean mParentNotFullyVisible;
@@ -690,7 +691,7 @@
}
private void updateAlgorithmHeightAndPadding() {
- if (mPulsing != null) {
+ if (mPulsing) {
mTopPadding = mClockBottom;
} else {
mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
@@ -920,6 +921,27 @@
}
/**
+ * @return the height of the top heads up notification when pinned. This is different from the
+ * intrinsic height, which also includes whether the notification is system expanded and
+ * is mainly used when dragging down from a heads up notification.
+ */
+ private int getTopHeadsUpPinnedHeight() {
+ NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ if (topEntry == null) {
+ return 0;
+ }
+ ExpandableNotificationRow row = topEntry.row;
+ if (row.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null) {
+ row = groupSummary;
+ }
+ }
+ return row.getPinnedHeadsUpHeight();
+ }
+
+ /**
* @return the position from where the appear transition ends when expanding.
* Measured in absolute height.
*/
@@ -930,7 +952,7 @@
int minNotificationsForShelf = 1;
if (mTrackingHeadsUp
|| (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
- appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+ appearPosition = getTopHeadsUpPinnedHeight();
minNotificationsForShelf = 2;
} else {
appearPosition = 0;
@@ -1198,9 +1220,9 @@
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mHeadsUpManager.getTopEntry().entry.row != row
+ && mHeadsUpManager.getTopEntry().row != row
&& mGroupManager.getGroupSummary(
- mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+ mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
!= row) {
continue;
}
@@ -2120,7 +2142,7 @@
@Override
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
private void updateScrollability() {
@@ -2753,7 +2775,7 @@
}
private boolean isClickedHeadsUp(View child) {
- return HeadsUpManager.isClickedHeadsUpNotification(child);
+ return HeadsUpUtil.isClickedHeadsUpNotification(child);
}
/**
@@ -4258,7 +4280,7 @@
mAnimationFinishedRunnables.add(runnable);
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
mAmbientState.setHeadsUpManager(headsUpManager);
}
@@ -4326,8 +4348,8 @@
return mIsExpanded;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) {
- if (mPulsing == null && pulsing == null) {
+ public void setPulsing(boolean pulsing, int clockBottom) {
+ if (!mPulsing && !pulsing) {
return;
}
mPulsing = pulsing;
@@ -4466,7 +4488,7 @@
pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
+ " alpha:%f scrollY:%d]",
this.getClass().getSimpleName(),
- mPulsing != null ?"T":"f",
+ mPulsing ? "T":"f",
mAmbientState.isQsCustomizerShowing() ? "T":"f",
getVisibility() == View.VISIBLE ? "visible"
: getVisibility() == View.GONE ? "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 682b849..04a7bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -30,7 +30,7 @@
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
/**
* A state of a view. This can be used to apply a set of view properties to a view with
@@ -582,7 +582,7 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- HeadsUpManager.setIsClickedNotification(child, false);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
child.setTag(TAG_START_TRANSLATION_Y, null);
child.setTag(TAG_END_TRANSLATION_Y, null);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index 5c888ac..6ed07f8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -49,6 +49,8 @@
import android.view.Window;
import android.view.WindowManager;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.Dependency;
@@ -81,6 +83,7 @@
private final MediaRouterWrapper mRouter;
private final MediaRouterCallback mRouterCallback;
private long mLastUpdateTime;
+ static final boolean INCLUDE_MEDIA_ROUTES = false;
private boolean mIsInCall;
protected boolean isAttached;
@@ -174,7 +177,7 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
- if (!mIsInCall) {
+ if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
mRouter.addCallback(mRouteSelector, mRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
@@ -201,6 +204,7 @@
@Override
public void show() {
super.show();
+ Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
mHardwareLayout.setTranslationX(getAnimTranslation());
mHardwareLayout.setAlpha(0);
mHardwareLayout.animate()
@@ -214,6 +218,7 @@
@Override
public void dismiss() {
+ Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
mHardwareLayout.setTranslationX(0);
mHardwareLayout.setAlpha(1);
mHardwareLayout.animate()
@@ -236,11 +241,15 @@
if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
mBluetoothController.connect(device);
}
} else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
if (route.isEnabled()) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
route.select();
}
}
@@ -251,8 +260,12 @@
if (item == null || item.tag == null) return;
if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
mBluetoothController.disconnect(device);
} else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
}
}
@@ -272,7 +285,7 @@
addBluetoothDevices(items);
// Add remote displays
- if (!mIsInCall) {
+ if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
addRemoteDisplayRoutes(items);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index efa8386..0203c43 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,16 +27,15 @@
import android.view.WindowManager.LayoutParams;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.tuner.TunerService;
import java.io.FileDescriptor;
@@ -53,8 +52,8 @@
public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb";
public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false;
- public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true;
- public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true;
+ public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
+ public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
private final SystemUI mSysui;
private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index a131a61..c622677 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -396,13 +396,9 @@
if (hasVibrator) {
mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
} else {
- final boolean wasZero = ss.level == 0;
- mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
}
} else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- final boolean wasZero = ss.level == 0;
- mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
} else {
mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 936ff51..59a7da6 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -65,7 +65,6 @@
LOCAL_JAVA_LIBRARIES := \
android.test.runner \
telephony-common \
- android.car \
android.test.base \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 521d2e3..8c4fd73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
import android.os.Looper;
import android.support.test.filters.SmallTest;
@@ -35,11 +36,14 @@
import android.view.Display;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.utils.os.FakeHandler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -48,12 +52,16 @@
DozeServiceFake mServiceFake;
DozeScreenState mScreen;
FakeHandler mHandlerFake;
+ @Mock
+ DozeParameters mDozeParameters;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
mServiceFake = new DozeServiceFake();
mHandlerFake = new FakeHandler(Looper.getMainLooper());
- mScreen = new DozeScreenState(mServiceFake, mHandlerFake);
+ mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeParameters);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 3e37cfe..bf6cc53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -151,22 +151,4 @@
verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
}
-
- @Test
- public void testGetTimeRemainingFormatted_roundsDownTo15() {
- mPowerNotificationWarnings.updateEstimate(
- new Estimate(TimeUnit.MINUTES.toMillis(57), true));
- String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
-
- assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M));
- }
-
- @Test
- public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() {
- mPowerNotificationWarnings.updateEstimate(
- new Estimate(TimeUnit.MINUTES.toMillis(65), true));
- String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
-
- assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR));
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 0a51e5a..4b455ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -155,7 +155,7 @@
// hybrid but the threshold has been overriden to be too low
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+ ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertFalse(shouldShow);
}
@@ -172,7 +172,7 @@
// hybrid since the threshold has been overriden to be much higher
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+ ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertTrue(shouldShow);
}
@@ -188,7 +188,7 @@
// hybrid
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+ ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertTrue(shouldShow);
}
@@ -203,7 +203,7 @@
// unplugged device that would show the non-hybrid notification and the hybrid
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+ BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertTrue(shouldShow);
}
@@ -218,7 +218,7 @@
// unplugged device that would show the non-hybrid but not the hybrid
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertTrue(shouldShow);
}
@@ -233,7 +233,7 @@
// unplugged device that would show the neither due to battery level being good
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, ABOVE_HYBRID_THRESHOLD,
+ ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertFalse(shouldShow);
}
@@ -248,7 +248,7 @@
// plugged device that would show the neither due to being plugged
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+ BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertFalse(shouldShow);
}
@@ -263,7 +263,7 @@
// Unknown battery status device that would show the neither due
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+ BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
!POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN);
assertFalse(shouldShow);
}
@@ -278,12 +278,31 @@
// BatterySaverEnabled device that would show the neither due to battery saver
boolean shouldShow =
mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+ BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
!POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
assertFalse(shouldShow);
}
@Test
+ public void testShouldShowLowBatteryWarning_onlyShowsOncePerChargeCycle() {
+ mPowerUI.start();
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ when(mEnhancedEstimates.getEstimate())
+ .thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true));
+ mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+
+ mPowerUI.maybeShowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET);
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertFalse(shouldShow);
+ }
+
+ @Test
public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() {
mPowerUI.start();
when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 6e7477f..f3c1171 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationInflaterTest;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -51,7 +52,7 @@
public NotificationTestHelper(Context context) {
mContext = context;
mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager);
+ mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
}
public ExpandableNotificationRow createRow() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
new file mode 100644
index 0000000..aa991cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.Notification;
+import android.os.UserHandle;
+import android.view.View;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HeadsUpManagerPhoneTest extends SysuiTestCase {
+ @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+
+ private HeadsUpManagerPhone mHeadsUpManager;
+
+ private NotificationData.Entry mEntry;
+ private StatusBarNotification mSbn;
+
+ @Mock private NotificationGroupManager mGroupManager;
+ @Mock private View mStatusBarWindowView;
+ @Mock private StatusBar mBar;
+ @Mock private ExpandableNotificationRow mRow;
+ @Mock private VisualStabilityManager mVSManager;
+
+ @Before
+ public void setUp() {
+ when(mVSManager.isReorderingAllowed()).thenReturn(true);
+
+ mHeadsUpManager = new HeadsUpManagerPhone(
+ mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager);
+
+ Notification.Builder n = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+ 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+
+ mEntry = new NotificationData.Entry(mSbn);
+ mEntry.row = mRow;
+ mEntry.expandedIcon = mock(StatusBarIconView.class);
+ }
+
+ @Test
+ public void testBasicOperations() {
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Update the notification.
+ mHeadsUpManager.updateNotification(mEntry, false);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Try to remove but defer, since the notification is currenlt visible on display.
+ mHeadsUpManager.removeNotification(mEntry.key, false /* ignoreEarliestRemovalTime */);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Remove forcibly with ignoreEarliestRemovalTime = true.
+ mHeadsUpManager.removeNotification(mEntry.key, true /* ignoreEarliestRemovalTime */);
+
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+
+ @Test
+ public void testsTimeoutRemoval() {
+ mHeadsUpManager.removeMinimumDisplayTimeForTesting();
+
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // Run the code on the main thready, not to run an async operations.
+ instrumentation.runOnMainSync(() -> {
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ // Ensure the head up is visible before timeout.
+ assertNotNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNotNull(mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+ });
+ // Wait for the async operations, which removes the heads up notification.
+ waitForIdleSync();
+
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+
+ @Test
+ public void releaseImmediately() {
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Remove but defer, since the notification is visible on display.
+ mHeadsUpManager.releaseImmediately(mEntry.key);
+
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+
+ @Test
+ public void releaseAllImmediately() {
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Remove but defer, since the notification is visible on display.
+ mHeadsUpManager.releaseAllImmediately();
+
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 6d2691c..43e16db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -38,6 +38,7 @@
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.Choreographer;
import android.view.View;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -374,7 +375,6 @@
onPreDraw();
// Force finish screen blanking.
- endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK);
mHandler.dispatchQueuedMessages();
// Force finish all animations.
endAnimation(mScrimBehind, TAG_KEY_ANIM);
@@ -401,6 +401,15 @@
protected WakeLock createWakeLock() {
return mWakeLock;
}
+
+ /**
+ * Do not wait for a frame since we're in a test environment.
+ * @param callback What to execute.
+ */
+ @Override
+ protected void doOnTheNextFrame(Choreographer.FrameCallback callback) {
+ callback.doFrame(0);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index bdf9b1f..31442af 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
@@ -86,8 +86,8 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -110,7 +110,7 @@
@Mock private UnlockMethodCache mUnlockMethodCache;
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private NotificationStackScrollLayout mStackScroller;
- @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private SystemServicesProxy mSystemServicesProxy;
@Mock private NotificationPanelView mNotificationPanelView;
@Mock private IStatusBarService mBarService;
@@ -588,7 +588,7 @@
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
- NotificationStackScrollLayout stack, HeadsUpManager hum,
+ NotificationStackScrollLayout stack, HeadsUpManagerPhone hum,
PowerManager pm, NotificationPanelView panelView,
IStatusBarService barService, NotificationListener notificationListener,
NotificationLogger notificationLogger,
@@ -650,7 +650,7 @@
public void setUpForTest(NotificationPresenter presenter,
NotificationListContainer listContainer,
Callback callback,
- HeadsUpManager headsUpManager,
+ HeadsUpManagerPhone headsUpManager,
NotificationData notificationData) {
super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
mNotificationData = notificationData;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index a10bebf..e1b97bda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -29,12 +29,14 @@
import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
+@Ignore
public class LocationControllerImplTest extends SysuiTestCase {
private LocationControllerImpl mLocationController;
@@ -79,4 +81,4 @@
TestableLooper.get(this).processAllMessages();
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
index f7bb065..922bde7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.volume;
+import static com.android.systemui.volume.OutputChooserDialog.INCLUDE_MEDIA_ROUTES;
+
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -149,19 +151,13 @@
}
@Test
- public void testMediaScanIfNotInCall() {
- mDialog.setIsInCall(false);
- mDialog.onAttachedToWindow();
-
- verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
- }
-
- @Test
public void testRegisterCallbacks() {
mDialog.setIsInCall(false);
mDialog.onAttachedToWindow();
- verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
+ if (INCLUDE_MEDIA_ROUTES) {
+ verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
+ }
verify(mController, times(1)).addCallback(any());
verify(mVolumeController, times(1)).addCallback(any(), any());
}
diff --git a/proto/Android.bp b/proto/Android.bp
index 95f453c..f3811bd 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -6,6 +6,8 @@
},
srcs: ["src/**/*.proto"],
no_framework_libs: true,
+ // Pin java_version until jarjar is certified to support later versions. http://b/72703434
+ java_version: "1.8",
target: {
android: {
jarjar_rules: "jarjar-rules.txt",
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 01714cf..ae5e133 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5171,6 +5171,7 @@
// An autofill service was bound using an unofficial(but still supported) permission.
// Package: Package of the autofill service
// OS: P
+
AUTOFILL_INVALID_PERMISSION = 1289;
// OPEN: QS Alarm tile shown
@@ -5185,6 +5186,33 @@
// OS: P
USB_DEVICE_DETAILS = 1291;
+ // OPEN: Settings > Accessibility > Vibration
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACCESSIBILITY_VIBRATION = 1292;
+
+ // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293;
+
+ // OPEN: Settings > Accessibility > Vibration > Touch vibration
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACCESSIBILITY_VIBRATION_TOUCH = 1294;
+
+ // OPEN: Volume panel > output chooser dialog
+ // OS: P
+ OUTPUT_CHOOSER = 1295;
+
+ // Action: Volume panel > output chooser dialog > tap on device
+ // OS: P
+ ACTION_OUTPUT_CHOOSER_CONNECT = 1296;
+
+ // Action: Volume panel > output chooser dialog > tap on X next to connected device
+ // OS: P
+ ACTION_OUTPUT_CHOOSER_DISCONNECT = 1297;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index b32be73..52d0e08e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -24,8 +24,9 @@
#include <utils/misc.h>
#include <inttypes.h>
+#include <android-base/macros.h>
#include <androidfw/Asset.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include <androidfw/ResourceTypes.h>
#include <android-base/macros.h>
@@ -1664,18 +1665,22 @@
static jlong
nFileA3DCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path)
{
- AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr);
+ Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr);
if (mgr == nullptr) {
return 0;
}
AutoJavaStringToUTF8 str(_env, _path);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- if (asset == nullptr) {
- return 0;
+ std::unique_ptr<Asset> asset;
+ {
+ ScopedLock<AssetManager2> locked_mgr(*mgr);
+ asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ return 0;
+ }
}
- jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset);
+ jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset.release());
return id;
}
@@ -1752,22 +1757,25 @@
nFontCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path,
jfloat fontSize, jint dpi)
{
- AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr);
+ Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr);
if (mgr == nullptr) {
return 0;
}
AutoJavaStringToUTF8 str(_env, _path);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- if (asset == nullptr) {
- return 0;
+ std::unique_ptr<Asset> asset;
+ {
+ ScopedLock<AssetManager2> locked_mgr(*mgr);
+ asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ return 0;
+ }
}
jlong id = (jlong)(uintptr_t)rsFontCreateFromMemory((RsContext)con,
str.c_str(), str.length(),
fontSize, dpi,
asset->getBuffer(false), asset->getLength());
- delete asset;
return id;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index a70b88e..c36bb6d 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -901,6 +901,7 @@
}
}
+ @GuardedBy("mLock")
private void setMagnificationSpecLocked(MagnificationSpec spec) {
if (mEnabled) {
if (DEBUG_SET_MAGNIFICATION_SPEC) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72c3c94..d5a722b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -306,6 +306,7 @@
*
* @return service instance.
*/
+ @GuardedBy("mLock")
@NonNull
AutofillManagerServiceImpl getServiceForUserLocked(int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -325,6 +326,7 @@
*
* @return service instance or {@code null} if not already present
*/
+ @GuardedBy("mLock")
@Nullable
AutofillManagerServiceImpl peekServiceForUserLocked(int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -474,6 +476,7 @@
/**
* Removes a cached service for a given user.
*/
+ @GuardedBy("mLock")
private void removeCachedServiceLocked(int userId) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
@@ -485,6 +488,7 @@
/**
* Updates a cached service for a given user.
*/
+ @GuardedBy("mLock")
private void updateCachedServiceLocked(int userId) {
updateCachedServiceLocked(userId, mDisabledUsers.get(userId));
}
@@ -492,6 +496,7 @@
/**
* Updates a cached service for a given user.
*/
+ @GuardedBy("mLock")
private void updateCachedServiceLocked(int userId, boolean disabled) {
AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 297dcf1..2dcc6da 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -211,6 +211,7 @@
}
}
+ @GuardedBy("mLock")
private int getServiceUidLocked() {
if (mInfo == null) {
Slog.w(TAG, "getServiceUidLocked(): no mInfo");
@@ -248,6 +249,7 @@
mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
}
+ @GuardedBy("mLock")
void updateLocked(boolean disabled) {
final boolean wasEnabled = isEnabledLocked();
if (sVerbose) {
@@ -300,6 +302,7 @@
}
}
+ @GuardedBy("mLock")
boolean addClientLocked(IAutoFillManagerClient client) {
if (mClients == null) {
mClients = new RemoteCallbackList<>();
@@ -308,12 +311,14 @@
return isEnabledLocked();
}
+ @GuardedBy("mLock")
void removeClientLocked(IAutoFillManagerClient client) {
if (mClients != null) {
mClients.unregister(client);
}
}
+ @GuardedBy("mLock")
void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
if (!isEnabledLocked()) {
return;
@@ -336,6 +341,7 @@
}
}
+ @GuardedBy("mLock")
int startSessionLocked(@NonNull IBinder activityToken, int uid,
@NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
@NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
@@ -389,6 +395,7 @@
/**
* Remove abandoned sessions if needed.
*/
+ @GuardedBy("mLock")
private void pruneAbandonedSessionsLocked() {
long now = System.currentTimeMillis();
if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
@@ -400,6 +407,7 @@
}
}
+ @GuardedBy("mLock")
void finishSessionLocked(int sessionId, int uid) {
if (!isEnabledLocked()) {
return;
@@ -423,6 +431,7 @@
}
}
+ @GuardedBy("mLock")
void cancelSessionLocked(int sessionId, int uid) {
if (!isEnabledLocked()) {
return;
@@ -436,6 +445,7 @@
session.removeSelfLocked();
}
+ @GuardedBy("mLock")
void disableOwnedAutofillServicesLocked(int uid) {
Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
if (mInfo == null) return;
@@ -468,6 +478,7 @@
}
}
+ @GuardedBy("mLock")
private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
@NonNull IBinder appCallbackToken, boolean hasCallback,
@NonNull ComponentName componentName, int flags) {
@@ -546,6 +557,7 @@
/**
* Updates a session and returns whether it should be restarted.
*/
+ @GuardedBy("mLock")
boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
AutofillValue value, int action, int flags) {
final Session session = mSessions.get(sessionId);
@@ -568,6 +580,7 @@
return false;
}
+ @GuardedBy("mLock")
void removeSessionLocked(int sessionId) {
mSessions.remove(sessionId);
}
@@ -603,6 +616,7 @@
}
}
+ @GuardedBy("mLock")
void destroyLocked() {
if (sVerbose) Slog.v(TAG, "destroyLocked()");
@@ -655,6 +669,7 @@
}
}
+ @GuardedBy("mLock")
private boolean isValidEventLocked(String method, int sessionId) {
if (mEventHistory == null) {
Slog.w(TAG, method + ": not logging event because history is null");
@@ -726,6 +741,7 @@
/**
* Updates the last fill response when an autofill context is committed.
*/
+ @GuardedBy("mLock")
void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
@Nullable ArrayList<String> selectedDatasets,
@Nullable ArraySet<String> ignoredDatasets,
@@ -739,6 +755,7 @@
manuallyFilledDatasetIds, null, null, appPackageName);
}
+ @GuardedBy("mLock")
void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
@Nullable ArrayList<String> selectedDatasets,
@Nullable ArraySet<String> ignoredDatasets,
@@ -847,6 +864,7 @@
}
}
+ @GuardedBy("mLock")
private boolean isCalledByServiceLocked(String methodName, int callingUid) {
if (getServiceUidLocked() != callingUid) {
Slog.w(TAG, methodName + "() called by UID " + callingUid
@@ -856,13 +874,19 @@
return true;
}
+ @GuardedBy("mLock")
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("User: "); pw.println(mUserId);
pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked());
- pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
- ? mInfo.getServiceInfo().getComponentName() : null);
+ pw.print(prefix); pw.print("Autofill Service Info: ");
+ if (mInfo == null) {
+ pw.println("N/A");
+ } else {
+ pw.println();
+ mInfo.dump(prefix2, pw);
+ }
pw.print(prefix); pw.print("Component from settings: ");
pw.println(getComponentNameFromSettings());
pw.print(prefix); pw.print("Default component: ");
@@ -870,6 +894,7 @@
pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Field classification enabled: ");
pw.println(isFieldClassificationEnabledLocked());
+ pw.print(prefix); pw.print("Compat pkgs: "); pw.println(getWhitelistedCompatModePackages());
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
@@ -959,6 +984,7 @@
mFieldClassificationStrategy.dump(prefix2, pw);
}
+ @GuardedBy("mLock")
void destroySessionsLocked() {
if (mSessions.size() == 0) {
mUi.destroyAll(null, null, false);
@@ -970,6 +996,7 @@
}
// TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
+ @GuardedBy("mLock")
void destroyFinishedSessionsLocked() {
final int sessionCount = mSessions.size();
for (int i = sessionCount - 1; i >= 0; i--) {
@@ -981,6 +1008,7 @@
}
}
+ @GuardedBy("mLock")
void listSessionsLocked(ArrayList<String> output) {
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
@@ -989,6 +1017,7 @@
}
}
+ @GuardedBy("mLock")
boolean isCompatibilityModeRequestedLocked(@NonNull String packageName,
long versionCode) {
if (mInfo == null || !mInfo.isCompatibilityModeRequested(packageName, versionCode)) {
@@ -996,14 +1025,18 @@
}
if (!Build.IS_ENG) {
// TODO: Build a map and watch for settings changes (this is called on app start)
- final String whiteListedPackages = Settings.Global.getString(
- mContext.getContentResolver(),
- Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES);
+ final String whiteListedPackages = getWhitelistedCompatModePackages();
return whiteListedPackages != null && whiteListedPackages.contains(packageName);
}
return true;
}
+ private String getWhitelistedCompatModePackages() {
+ return Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES);
+ }
+
private void sendStateToClients(boolean resetClient) {
final RemoteCallbackList<IAutoFillManagerClient> clients;
final int userClientCount;
@@ -1050,6 +1083,7 @@
}
}
+ @GuardedBy("mLock")
private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
final int sessionCount = mSessions.size();
for (int i = 0; i < sessionCount; i++) {
@@ -1061,6 +1095,7 @@
return true;
}
+ @GuardedBy("mLock")
boolean isEnabledLocked() {
return mSetupComplete && mInfo != null && !mDisabled;
}
@@ -1113,6 +1148,7 @@
/**
* Checks if autofill is disabled by service to the given activity.
*/
+ @GuardedBy("mLock")
private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
// Check activities first.
long elapsedTime = 0;
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index aea9ad0..fe6d4c4 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -475,6 +475,7 @@
return true;
}
+ @GuardedBy("mLock")
protected boolean isCancelledLocked() {
return mCancelled;
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6108afa..4a24704 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -300,6 +300,7 @@
/**
* Returns the ids of all entries in {@link #mViewStates} in the same order.
*/
+ @GuardedBy("mLock")
private AutofillId[] getIdsOfAllViewStatesLocked() {
final int numViewState = mViewStates.size();
final AutofillId[] ids = new AutofillId[numViewState];
@@ -346,6 +347,7 @@
* <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
* or {@code null} when not found on either of them.
*/
+ @GuardedBy("mLock")
private AutofillValue findValueLocked(@NonNull AutofillId id) {
final ViewState state = mViewStates.get(id);
if (state == null) {
@@ -369,6 +371,7 @@
* @param fillContext The context to be filled
* @param flags The flags that started the session
*/
+ @GuardedBy("mLock")
private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
final ViewNode[] nodes = fillContext
.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
@@ -409,6 +412,7 @@
/**
* Cancels the last request sent to the {@link #mRemoteFillService}.
*/
+ @GuardedBy("mLock")
private void cancelCurrentRequestLocked() {
final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
@@ -430,6 +434,7 @@
/**
* Reads a new structure and then request a new fill response from the fill service.
*/
+ @GuardedBy("mLock")
private void requestNewFillResponseLocked(int flags) {
int requestId;
@@ -497,6 +502,7 @@
*
* @return The activity token
*/
+ @GuardedBy("mLock")
@NonNull IBinder getActivityTokenLocked() {
return mActivityToken;
}
@@ -663,6 +669,7 @@
*
* @return The context or {@code null} if there is no context
*/
+ @GuardedBy("mLock")
@Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
if (mContexts == null) {
return null;
@@ -820,6 +827,7 @@
});
}
+ @GuardedBy("mLock")
void setAuthenticationResultLocked(Bundle data, int authenticationId) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
@@ -882,6 +890,7 @@
}
}
+ @GuardedBy("mLock")
void setHasCallbackLocked(boolean hasIt) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
@@ -891,6 +900,7 @@
mHasCallback = hasIt;
}
+ @GuardedBy("mLock")
@Nullable
private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
if (mContexts == null) {
@@ -923,6 +933,7 @@
return response;
}
+ @GuardedBy("mLock")
@Nullable
private SaveInfo getSaveInfoLocked() {
final FillResponse response = getLastResponseLocked(null);
@@ -941,13 +952,14 @@
});
}
+ @GuardedBy("mLock")
private void logContextCommittedLocked() {
final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
if (lastResponse == null) return;
final int flags = lastResponse.getFlags();
if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
- if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
+ if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
return;
}
@@ -1241,6 +1253,7 @@
*
* @return {@code true} if session is done, or {@code false} if it's pending user action.
*/
+ @GuardedBy("mLock")
public boolean showSaveLocked() {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
@@ -1431,7 +1444,10 @@
}
}
- if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
+ if (sDebug) {
+ Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
+ + id + "!");
+ }
// Use handler so logContextCommitted() is logged first
mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState));
@@ -1455,7 +1471,7 @@
}
// Nothing changed...
if (sDebug) {
- Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
+ Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities."
+ "allRequiredAreNotNull=" + allRequiredAreNotEmpty
+ ", atLeastOneChanged=" + atLeastOneChanged);
}
@@ -1507,6 +1523,7 @@
/**
* Returns whether the session is currently showing the save UI
*/
+ @GuardedBy("mLock")
boolean isSavingLocked() {
return mIsSaving;
}
@@ -1514,6 +1531,7 @@
/**
* Gets the latest non-empty value for the given id in the autofill contexts.
*/
+ @GuardedBy("mLock")
@Nullable
private AutofillValue getValueFromContextsLocked(AutofillId id) {
final int numContexts = mContexts.size();
@@ -1536,6 +1554,7 @@
/**
* Gets the latest autofill options for the given id in the autofill contexts.
*/
+ @GuardedBy("mLock")
@Nullable
private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
final int numContexts = mContexts.size();
@@ -1553,6 +1572,7 @@
/**
* Calls service when user requested save.
*/
+ @GuardedBy("mLock")
void callSaveLocked() {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
@@ -1643,6 +1663,7 @@
* @param viewState The view that is entered.
* @param flags The flag that was passed by the AutofillManager.
*/
+ @GuardedBy("mLock")
private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
@NonNull ViewState viewState, int flags) {
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
@@ -1670,6 +1691,7 @@
*
* @return {@code true} iff a new partition should be started
*/
+ @GuardedBy("mLock")
private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
if (mResponses == null) {
return true;
@@ -1718,6 +1740,7 @@
return true;
}
+ @GuardedBy("mLock")
void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
int flags) {
if (mDestroyed) {
@@ -1827,6 +1850,7 @@
/**
* Checks whether a view should be ignored.
*/
+ @GuardedBy("mLock")
private boolean isIgnoredLocked(AutofillId id) {
// Always check the latest response only
final FillResponse response = getLastResponseLocked(null);
@@ -1907,6 +1931,7 @@
}
}
+ @GuardedBy("mLock")
private void updateTrackedIdsLocked() {
// Only track the views of the last response as only those are reported back to the
// service, see #showSaveLocked
@@ -1970,7 +1995,7 @@
try {
if (sVerbose) {
Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
- + " (triggering on " + saveTriggerId + ")");
+ + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish);
}
mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
saveOnFinish, toArray(fillableIds), saveTriggerId);
@@ -1979,6 +2004,7 @@
}
}
+ @GuardedBy("mLock")
private void replaceResponseLocked(@NonNull FillResponse oldResponse,
@NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
// Disassociate view states with the old response
@@ -2002,6 +2028,7 @@
removeSelf();
}
+ @GuardedBy("mLock")
private void processResponseLocked(@NonNull FillResponse newResponse,
@Nullable Bundle newClientState, int flags) {
// Make sure we are hiding the UI which will be shown
@@ -2039,6 +2066,7 @@
/**
* Sets the state of all views in the given response.
*/
+ @GuardedBy("mLock")
private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
final List<Dataset> datasets = response.getDatasets();
if (datasets != null) {
@@ -2087,6 +2115,7 @@
/**
* Sets the state of all views in the given dataset and response.
*/
+ @GuardedBy("mLock")
private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
int state, boolean clearResponse) {
final ArrayList<AutofillId> ids = dataset.getFieldIds();
@@ -2107,6 +2136,7 @@
}
}
+ @GuardedBy("mLock")
private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
@Nullable AutofillValue value) {
ViewState viewState = mViewStates.get(id);
@@ -2168,6 +2198,7 @@
}
// TODO: this should never be null, but we got at least one occurrence, probably due to a race.
+ @GuardedBy("mLock")
@Nullable
private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
final Intent fillInIntent = new Intent();
@@ -2200,6 +2231,7 @@
return "Session: [id=" + id + ", component=" + mComponentName + "]";
}
+ @GuardedBy("mLock")
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("id: "); pw.println(id);
@@ -2329,6 +2361,7 @@
* disabled it).
* </ul>
*/
+ @GuardedBy("mLock")
RemoteFillService destroyLocked() {
if (mDestroyed) {
return null;
@@ -2344,6 +2377,7 @@
* Cleans up this session and remove it from the service always, even if it does have a pending
* Save UI.
*/
+ @GuardedBy("mLock")
void forceRemoveSelfLocked() {
if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
@@ -2373,6 +2407,7 @@
* Cleans up this session and remove it from the service, but but only if it does not have a
* pending Save UI.
*/
+ @GuardedBy("mLock")
void removeSelfLocked() {
if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
if (mDestroyed) {
@@ -2401,6 +2436,7 @@
* a specific {@code token} created by
* {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
*/
+ @GuardedBy("mLock")
boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
}
@@ -2408,10 +2444,12 @@
/**
* Checks whether this session is hiding the Save UI to handle a custom description link.
*/
+ @GuardedBy("mLock")
private boolean isSaveUiPendingLocked() {
return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
}
+ @GuardedBy("mLock")
private int getLastResponseIndexLocked() {
// The response ids are monotonically increasing so
// we just find the largest id which is the last. We
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3b80f55..83367f3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1931,6 +1931,7 @@
/**
* Remove a package from the full-data queue.
*/
+ @GuardedBy("mQueueLock")
private void dequeueFullBackupLocked(String packageName) {
final int N = mFullBackupQueue.size();
for (int i = N - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index f49cd67..7ab5812 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -58,6 +58,7 @@
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
@@ -79,7 +80,6 @@
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Random;
@@ -88,6 +88,7 @@
import java.util.function.Predicate;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -224,10 +225,12 @@
interface Stats {
int REBATCH_ALL_ALARMS = 0;
+ int REORDER_ALARMS_FOR_STANDBY = 1;
}
private final StatLogger mStatLogger = new StatLogger(new String[] {
"REBATCH_ALL_ALARMS",
+ "REORDER_ALARMS_FOR_STANDBY",
});
/**
@@ -522,6 +525,10 @@
return newStart;
}
+ boolean remove(Alarm alarm) {
+ return remove(a -> (a == alarm));
+ }
+
boolean remove(Predicate<Alarm> predicate) {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
@@ -741,6 +748,23 @@
return (index == 0);
}
+ private void insertAndBatchAlarmLocked(Alarm alarm) {
+ final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
+ : attemptCoalesceLocked(alarm.whenElapsed, alarm.maxWhenElapsed);
+
+ if (whichBatch < 0) {
+ addBatchLocked(mAlarmBatches, new Batch(alarm));
+ } else {
+ final Batch batch = mAlarmBatches.get(whichBatch);
+ if (batch.add(alarm)) {
+ // The start time of this batch advanced, so batch ordering may
+ // have just been broken. Move it to where it now belongs.
+ mAlarmBatches.remove(whichBatch);
+ addBatchLocked(mAlarmBatches, batch);
+ }
+ }
+ }
+
// Return the index of the matching batch, or -1 if none found.
int attemptCoalesceLocked(long whenElapsed, long maxWhen) {
final int N = mAlarmBatches.size();
@@ -794,7 +818,7 @@
}
void rebatchAllAlarmsLocked(boolean doValidate) {
- long start = mStatLogger.getTime();
+ final long start = mStatLogger.getTime();
final int oldCount =
getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
@@ -837,6 +861,44 @@
mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start);
}
+ /**
+ * Re-orders the alarm batches based on newly evaluated send times based on the current
+ * app-standby buckets
+ * @param targetPackages [Package, User] pairs for which alarms need to be re-evaluated,
+ * null indicates all
+ * @return True if there was any reordering done to the current list.
+ */
+ boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
+ final long start = mStatLogger.getTime();
+ final ArrayList<Alarm> rescheduledAlarms = new ArrayList<>();
+
+ for (int batchIndex = mAlarmBatches.size() - 1; batchIndex >= 0; batchIndex--) {
+ final Batch batch = mAlarmBatches.get(batchIndex);
+ for (int alarmIndex = batch.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = batch.get(alarmIndex);
+ final Pair<String, Integer> packageUser =
+ Pair.create(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid));
+ if (targetPackages != null && !targetPackages.contains(packageUser)) {
+ continue;
+ }
+ if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+ batch.remove(alarm);
+ rescheduledAlarms.add(alarm);
+ }
+ }
+ if (batch.size() == 0) {
+ mAlarmBatches.remove(batchIndex);
+ }
+ }
+ for (int i = 0; i < rescheduledAlarms.size(); i++) {
+ final Alarm a = rescheduledAlarms.get(i);
+ insertAndBatchAlarmLocked(a);
+ }
+
+ mStatLogger.logDurationStat(Stats.REORDER_ALARMS_FOR_STANDBY, start);
+ return rescheduledAlarms.size() > 0;
+ }
+
void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
a.when = a.origWhen;
long whenElapsed = convertToElapsed(a.when, a.type);
@@ -950,7 +1012,7 @@
// Recurring alarms may have passed several alarm intervals while the
// alarm was kept pending. Send the appropriate trigger count.
if (alarm.repeatInterval > 0) {
- alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval;
+ alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
final long nextElapsed = alarm.whenElapsed + delta;
@@ -1442,18 +1504,27 @@
else return mConstants.APP_STANDBY_MIN_DELAYS[0];
}
- private void adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
- if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) {
- return;
+ /**
+ * Adjusts the alarm delivery time based on the current app standby bucket.
+ * @param alarm The alarm to adjust
+ * @return true if the alarm delivery time was updated.
+ */
+ private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
+ if (isExemptFromAppStandby(alarm)) {
+ return false;
}
if (mAppStandbyParole) {
- if (alarm.whenElapsed > alarm.requestedWhenElapsed) {
- // We did throttle this alarm earlier, restore original requirements
- alarm.whenElapsed = alarm.requestedWhenElapsed;
- alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
+ if (alarm.whenElapsed > alarm.expectedWhenElapsed) {
+ // We did defer this alarm earlier, restore original requirements
+ alarm.whenElapsed = alarm.expectedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+ return true;
}
- return;
+ return false;
}
+ final long oldWhenElapsed = alarm.whenElapsed;
+ final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
+
final String sourcePackage = alarm.sourcePackage;
final int sourceUserId = UserHandle.getUserId(alarm.creatorUid);
final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
@@ -1463,10 +1534,16 @@
final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
if (lastElapsed > 0) {
final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
- if (alarm.requestedWhenElapsed < minElapsed) {
+ if (alarm.expectedWhenElapsed < minElapsed) {
alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+ } else {
+ // app is now eligible to run alarms at the originally requested window.
+ // Restore original requirements in case they were changed earlier.
+ alarm.whenElapsed = alarm.expectedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
}
}
+ return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
}
private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
@@ -1521,21 +1598,7 @@
}
}
adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
-
- int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
- ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
- if (whichBatch < 0) {
- Batch batch = new Batch(a);
- addBatchLocked(mAlarmBatches, batch);
- } else {
- Batch batch = mAlarmBatches.get(whichBatch);
- if (batch.add(a)) {
- // The start time of this batch advanced, so batch ordering may
- // have just been broken. Move it to where it now belongs.
- mAlarmBatches.remove(whichBatch);
- addBatchLocked(mAlarmBatches, batch);
- }
- }
+ insertAndBatchAlarmLocked(a);
if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
@@ -1661,7 +1724,7 @@
// This means we will allow these alarms to go off as normal even while idle, with no
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
- || callingUid == mSystemUiUid
+ || UserHandle.isSameApp(callingUid, mSystemUiUid)
|| mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
@@ -2933,10 +2996,11 @@
// Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
// alarm went off for this app. Reschedule the alarm to be in the
// correct time period.
- alarm.whenElapsed = minTime;
+ alarm.expectedWhenElapsed = alarm.whenElapsed = minTime;
if (alarm.maxWhenElapsed < minTime) {
alarm.maxWhenElapsed = minTime;
}
+ alarm.expectedMaxWhenElapsed = alarm.maxWhenElapsed;
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
ent.uid = alarm.uid;
@@ -2986,7 +3050,7 @@
if (alarm.repeatInterval > 0) {
// this adjustment will be zero if we're late by
// less than one full repeat interval
- alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval;
+ alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
@@ -3061,8 +3125,9 @@
public long windowLength;
public long whenElapsed; // 'when' in the elapsed time base
public long maxWhenElapsed; // also in the elapsed time base
- public final long requestedWhenElapsed; // original expiry time requested by the app
- public final long requestedMaxWhenElapsed;
+ // Expected alarm expiry time before app standby deferring is applied.
+ public long expectedWhenElapsed;
+ public long expectedMaxWhenElapsed;
public long repeatInterval;
public PriorityClass priorityClass;
@@ -3076,10 +3141,10 @@
|| _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
- requestedWhenElapsed = _whenElapsed;
+ expectedWhenElapsed = _whenElapsed;
windowLength = _windowLength;
maxWhenElapsed = _maxWhen;
- requestedMaxWhenElapsed = _maxWhen;
+ expectedMaxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
listener = _rec;
@@ -3138,8 +3203,10 @@
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
pw.print(prefix); pw.print("tag="); pw.println(statsTag);
pw.print(prefix); pw.print("type="); pw.print(type);
- pw.print(" requestedWhenELapsed="); TimeUtils.formatDuration(
- requestedWhenElapsed, nowELAPSED, pw);
+ pw.print(" expectedWhenElapsed="); TimeUtils.formatDuration(
+ expectedWhenElapsed, nowELAPSED, pw);
+ pw.print(" expectedMaxWhenElapsed="); TimeUtils.formatDuration(
+ expectedMaxWhenElapsed, nowELAPSED, pw);
pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
nowELAPSED, pw);
pw.print(" when=");
@@ -3277,6 +3344,11 @@
}
}
+ private boolean isExemptFromAppStandby(Alarm a) {
+ return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
+ || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
+ }
+
private class AlarmThread extends Thread
{
public AlarmThread()
@@ -3391,28 +3463,19 @@
}
mPendingNonWakeupAlarms.clear();
}
- boolean needRebatch = false;
- final HashSet<String> triggerPackages = new HashSet<>();
- for (int i = triggerList.size() - 1; i >= 0; i--) {
- triggerPackages.add(triggerList.get(i).sourcePackage);
- }
- outer:
- for (int i = 0; i < mAlarmBatches.size(); i++) {
- final Batch batch = mAlarmBatches.get(i);
- for (int j = 0; j < batch.size(); j++) {
- if (triggerPackages.contains(batch.get(j))) {
- needRebatch = true;
- break outer;
- }
+ final ArraySet<Pair<String, Integer>> triggerPackages =
+ new ArraySet<>();
+ for (int i = 0; i < triggerList.size(); i++) {
+ final Alarm a = triggerList.get(i);
+ if (!isExemptFromAppStandby(a)) {
+ triggerPackages.add(Pair.create(
+ a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
}
}
- if (needRebatch) {
- rebatchAllAlarmsLocked(false);
- } else {
- rescheduleKernelAlarmsLocked();
- updateNextAlarmClockLocked();
- }
deliverAlarmsLocked(triggerList, nowELAPSED);
+ reorderAlarmsBasedOnStandbyBuckets(triggerPackages);
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
}
@@ -3470,10 +3533,15 @@
public static final int REPORT_ALARMS_ACTIVE = 4;
public static final int APP_STANDBY_BUCKET_CHANGED = 5;
public static final int APP_STANDBY_PAROLE_CHANGED = 6;
+ public static final int REMOVE_FOR_STOPPED = 7;
public AlarmHandler() {
}
+ public void postRemoveForStopped(int uid) {
+ obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
+ }
+
public void handleMessage(Message msg) {
switch (msg.what) {
case ALARM_EVENT: {
@@ -3518,13 +3586,27 @@
case APP_STANDBY_PAROLE_CHANGED:
synchronized (mLock) {
mAppStandbyParole = (Boolean) msg.obj;
- rebatchAllAlarmsLocked(false);
+ if (reorderAlarmsBasedOnStandbyBuckets(null)) {
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
break;
case APP_STANDBY_BUCKET_CHANGED:
synchronized (mLock) {
- rebatchAllAlarmsLocked(false);
+ final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
+ filterPackages.add(Pair.create((String) msg.obj, msg.arg1));
+ if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) {
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+ }
+ break;
+
+ case REMOVE_FOR_STOPPED:
+ synchronized (mLock) {
+ removeForStoppedLocked(msg.arg1);
}
break;
@@ -3717,10 +3799,8 @@
}
@Override public void onUidGone(int uid, boolean disabled) {
- synchronized (mLock) {
- if (disabled) {
- removeForStoppedLocked(uid);
- }
+ if (disabled) {
+ mHandler.postRemoveForStopped(uid);
}
}
@@ -3728,10 +3808,8 @@
}
@Override public void onUidIdle(int uid, boolean disabled) {
- synchronized (mLock) {
- if (disabled) {
- removeForStoppedLocked(uid);
- }
+ if (disabled) {
+ mHandler.postRemoveForStopped(uid);
}
}
@@ -3751,7 +3829,8 @@
bucket);
}
mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
- mHandler.sendEmptyMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
+ mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
+ .sendToTarget();
}
@Override
@@ -3982,6 +4061,7 @@
/**
* Deliver an alarm and set up the post-delivery handling appropriately
*/
+ @GuardedBy("mLock")
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
if (alarm.operation != null) {
// PendingIntent alarm
@@ -4074,7 +4154,7 @@
mAllowWhileIdleDispatches.add(ent);
}
}
- if (!UserHandle.isCore(alarm.creatorUid)) {
+ if (!isExemptFromAppStandby(alarm)) {
final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid));
mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 339101f..b65d126 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -497,6 +497,7 @@
/**
* Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
*/
+ @GuardedBy("mLock")
private void refreshForcedAppStandbyUidPackagesLocked() {
mRunAnyRestrictedPackages.clear();
final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
@@ -536,6 +537,7 @@
/**
* Update {@link #mForceAllAppsStandby} and notifies the listeners.
*/
+ @GuardedBy("mLock")
private void toggleForceAllAppsStandbyLocked(boolean enable) {
if (enable == mForceAllAppsStandby) {
return;
@@ -545,6 +547,7 @@
mHandler.notifyForceAllAppsStandbyChanged();
}
+ @GuardedBy("mLock")
private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
final int size = mRunAnyRestrictedPackages.size();
if (size > 8) {
@@ -563,6 +566,7 @@
/**
* @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
*/
+ @GuardedBy("mLock")
boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
}
@@ -570,6 +574,7 @@
/**
* Add to / remove from {@link #mRunAnyRestrictedPackages}.
*/
+ @GuardedBy("mLock")
boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
boolean restricted) {
final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
@@ -614,48 +619,22 @@
private final class UidObserver extends IUidObserver.Stub {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq) {
- synchronized (mLock) {
- if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- if (removeUidFromArray(mForegroundUids, uid, false)) {
- mHandler.notifyUidForegroundStateChanged(uid);
- }
- } else {
- if (addUidToArray(mForegroundUids, uid)) {
- mHandler.notifyUidForegroundStateChanged(uid);
- }
- }
- }
- }
-
- @Override
- public void onUidGone(int uid, boolean disabled) {
- removeUid(uid, true);
+ mHandler.onUidStateChanged(uid, procState);
}
@Override
public void onUidActive(int uid) {
- synchronized (mLock) {
- if (addUidToArray(mActiveUids, uid)) {
- mHandler.notifyUidActiveStateChanged(uid);
- }
- }
+ mHandler.onUidActive(uid);
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ mHandler.onUidGone(uid, disabled);
}
@Override
public void onUidIdle(int uid, boolean disabled) {
- // Just to avoid excessive memcpy, don't remove from the array in this case.
- removeUid(uid, false);
- }
-
- private void removeUid(int uid, boolean remove) {
- synchronized (mLock) {
- if (removeUidFromArray(mActiveUids, uid, remove)) {
- mHandler.notifyUidActiveStateChanged(uid);
- }
- if (removeUidFromArray(mForegroundUids, uid, remove)) {
- mHandler.notifyUidForegroundStateChanged(uid);
- }
- }
+ mHandler.onUidIdle(uid, disabled);
}
@Override
@@ -740,6 +719,11 @@
private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
private static final int MSG_EXEMPT_CHANGED = 10;
+ private static final int MSG_ON_UID_STATE_CHANGED = 11;
+ private static final int MSG_ON_UID_ACTIVE = 12;
+ private static final int MSG_ON_UID_GONE = 13;
+ private static final int MSG_ON_UID_IDLE = 14;
+
public MyHandler(Looper looper) {
super(looper);
}
@@ -790,6 +774,22 @@
obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
}
+ public void onUidStateChanged(int uid, int procState) {
+ obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget();
+ }
+
+ public void onUidActive(int uid) {
+ obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget();
+ }
+
+ public void onUidGone(int uid, boolean disabled) {
+ obtainMessage(MSG_ON_UID_GONE, uid, disabled ? 1 : 0).sendToTarget();
+ }
+
+ public void onUidIdle(int uid, boolean disabled) {
+ obtainMessage(MSG_ON_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget();
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -883,6 +883,61 @@
case MSG_USER_REMOVED:
handleUserRemoved(msg.arg1);
return;
+
+ case MSG_ON_UID_STATE_CHANGED:
+ handleUidStateChanged(msg.arg1, msg.arg2);
+ return;
+ case MSG_ON_UID_ACTIVE:
+ handleUidActive(msg.arg1);
+ return;
+ case MSG_ON_UID_GONE:
+ handleUidGone(msg.arg1, msg.arg1 != 0);
+ return;
+ case MSG_ON_UID_IDLE:
+ handleUidIdle(msg.arg1, msg.arg1 != 0);
+ return;
+ }
+ }
+
+ public void handleUidStateChanged(int uid, int procState) {
+ synchronized (mLock) {
+ if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ if (removeUidFromArray(mForegroundUids, uid, false)) {
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
+ } else {
+ if (addUidToArray(mForegroundUids, uid)) {
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
+ }
+ }
+ }
+
+ public void handleUidActive(int uid) {
+ synchronized (mLock) {
+ if (addUidToArray(mActiveUids, uid)) {
+ mHandler.notifyUidActiveStateChanged(uid);
+ }
+ }
+ }
+
+ public void handleUidGone(int uid, boolean disabled) {
+ removeUid(uid, true);
+ }
+
+ public void handleUidIdle(int uid, boolean disabled) {
+ // Just to avoid excessive memcpy, don't remove from the array in this case.
+ removeUid(uid, false);
+ }
+
+ private void removeUid(int uid, boolean remove) {
+ synchronized (mLock) {
+ if (removeUidFromArray(mActiveUids, uid, remove)) {
+ mHandler.notifyUidActiveStateChanged(uid);
+ }
+ if (removeUidFromArray(mForegroundUids, uid, remove)) {
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 2f42585..bdeb231 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1166,6 +1166,7 @@
mImePackageAppeared = false;
}
+ @GuardedBy("mMethodMap")
private boolean shouldRebuildInputMethodListLocked() {
// This method is guaranteed to be called only by getRegisteredHandler().
@@ -1467,6 +1468,7 @@
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}
+ @GuardedBy("mMethodMap")
private void switchUserLocked(int newUserId) {
if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
+ " currentUserId=" + mSettings.getCurrentUserId());
@@ -1817,6 +1819,7 @@
return flags;
}
+ @GuardedBy("mMethodMap")
@NonNull
InputBindResult attachNewInputLocked(
/* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
@@ -1846,6 +1849,7 @@
mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
}
+ @GuardedBy("mMethodMap")
@NonNull
InputBindResult startInputLocked(
/* @InputMethodClient.StartInputReason */ final int startInputReason,
@@ -1889,6 +1893,7 @@
controlFlags, startInputReason);
}
+ @GuardedBy("mMethodMap")
@NonNull
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
@@ -2970,12 +2975,19 @@
break;
}
- if (!didStart && attribute != null) {
- if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
- || (controlFlags
- & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
- res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
- controlFlags, startInputReason);
+ if (!didStart) {
+ if (attribute != null) {
+ if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+ || (controlFlags
+ & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
+ res = startInputUncheckedLocked(cs, inputContext, missingMethods,
+ attribute,
+ controlFlags, startInputReason);
+ } else {
+ res = InputBindResult.NO_EDITOR;
+ }
+ } else {
+ res = InputBindResult.NULL_EDITOR_INFO;
}
}
}
@@ -3635,6 +3647,7 @@
return false;
}
+ @GuardedBy("mMethodMap")
void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index f4238f2..42c836e 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -199,6 +199,7 @@
* bound.
* @returns {@code true} if a valid package was found to bind to.
*/
+ @GuardedBy("mLock")
private boolean bindBestPackageLocked(String justCheckThisPackage, boolean forceRebind) {
Intent intent = new Intent(mAction);
if (justCheckThisPackage != null) {
@@ -273,6 +274,7 @@
return true;
}
+ @GuardedBy("mLock")
private void unbindLocked() {
ComponentName component;
component = mBoundComponent;
@@ -287,6 +289,7 @@
}
}
+ @GuardedBy("mLock")
private void bindToPackageLocked(ComponentName component, int version, int userId) {
Intent intent = new Intent(mAction);
intent.setComponent(component);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 84b93e3..1a0068d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -809,6 +809,7 @@
}
}
+ @GuardedBy("mLock")
private void addInternalVolumeLocked() {
// Create a stub volume that represents internal storage
final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
@@ -1109,6 +1110,7 @@
}
};
+ @GuardedBy("mLock")
private void onDiskScannedLocked(DiskInfo disk) {
int volumeCount = 0;
for (int i = 0; i < mVolumes.size(); i++) {
@@ -1134,6 +1136,7 @@
mCallbacks.notifyDiskScanned(disk, volumeCount);
}
+ @GuardedBy("mLock")
private void onVolumeCreatedLocked(VolumeInfo vol) {
if (mPms.isOnlyCoreApps()) {
Slog.d(TAG, "System booted in core-only mode; ignoring volume " + vol.getId());
@@ -1209,6 +1212,7 @@
return true;
}
+ @GuardedBy("mLock")
private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
@@ -1299,6 +1303,7 @@
}
}
+ @GuardedBy("mLock")
private void onMoveStatusLocked(int status) {
if (mMoveCallback == null) {
Slog.w(TAG, "Odd, status but no move requested");
@@ -1515,6 +1520,7 @@
}
}
+ @GuardedBy("mLock")
private void readSettingsLocked() {
mRecords.clear();
mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
@@ -1559,6 +1565,7 @@
}
}
+ @GuardedBy("mLock")
private void writeSettingsLocked() {
FileOutputStream fos = null;
try {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 6743484..6747be3 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -35,7 +35,6 @@
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.DisconnectCause;
-import android.telephony.LocationAccessPolicy;
import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
@@ -94,8 +93,7 @@
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
- int callerUid;
- int callerPid;
+ int callerUserId;
int events;
@@ -119,7 +117,7 @@
+ " callback=" + callback
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
- + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
+ + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
+ " events=" + Integer.toHexString(events)
+ " canReadPhoneState=" + canReadPhoneState + "}";
}
@@ -358,8 +356,6 @@
public void addOnSubscriptionsChangedListener(String callingPackage,
IOnSubscriptionsChangedListener callback) {
int callerUserId = UserHandle.getCallingUserId();
- mContext.getSystemService(AppOpsManager.class)
- .checkPackage(Binder.getCallingUid(), callingPackage);
if (VDBG) {
log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
+ " callerUserId=" + callerUserId + " callback=" + callback
@@ -403,8 +399,7 @@
r.onSubscriptionsChangedListenerCallback = callback;
r.callingPackage = callingPackage;
- r.callerUid = Binder.getCallingUid();
- r.callerPid = Binder.getCallingPid();
+ r.callerUserId = callerUserId;
r.events = 0;
r.canReadPhoneState = true; // permission has been enforced above
if (DBG) {
@@ -475,8 +470,6 @@
private void listen(String callingPackage, IPhoneStateListener callback, int events,
boolean notifyNow, int subId) {
int callerUserId = UserHandle.getCallingUserId();
- mContext.getSystemService(AppOpsManager.class)
- .checkPackage(Binder.getCallingUid(), callingPackage);
if (VDBG) {
log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events)
+ " notifyNow=" + notifyNow + " subId=" + subId + " myUserId="
@@ -521,8 +514,7 @@
r.callback = callback;
r.callingPackage = callingPackage;
- r.callerUid = Binder.getCallingUid();
- r.callerPid = Binder.getCallingPid();
+ r.callerUserId = callerUserId;
boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK
| ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0;
r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage);
@@ -580,10 +572,8 @@
try {
if (DBG_LOC) log("listen: mCellLocation = "
+ mCellLocation[phoneId]);
- if (checkLocationAccess(r)) {
- r.callback.onCellLocationChanged(
- new Bundle(mCellLocation[phoneId]));
- }
+ r.callback.onCellLocationChanged(
+ new Bundle(mCellLocation[phoneId]));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -629,9 +619,7 @@
try {
if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
+ mCellInfo.get(phoneId));
- if (checkLocationAccess(r)) {
- r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
- }
+ r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -991,8 +979,7 @@
mCellInfo.set(phoneId, cellInfo);
for (Record r : mRecords) {
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) &&
- idMatch(r.subId, subId, phoneId) &&
- checkLocationAccess(r)) {
+ idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r);
@@ -1275,8 +1262,7 @@
mCellLocation[phoneId] = cellLocation;
for (Record r : mRecords) {
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&
- idMatch(r.subId, subId, phoneId) &&
- checkLocationAccess(r)) {
+ idMatch(r.subId, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifyCellLocation: cellLocation=" + cellLocation
@@ -1720,11 +1706,10 @@
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
- valid = UserHandle.getUserId(r.callerUid) == foregroundUser
- && r.matchPhoneStateListenerEvent(events);
+ valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events);
if (DBG | DBG_LOC) {
log("validateEventsAndUserLocked: valid=" + valid
- + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
+ + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser
+ " r.events=" + r.events + " events=" + events);
}
} finally {
@@ -1756,16 +1741,6 @@
}
}
- private boolean checkLocationAccess(Record r) {
- long token = Binder.clearCallingIdentity();
- try {
- return LocationAccessPolicy.canAccessCellLocation(mContext,
- r.callingPackage, r.callerUid, r.callerPid);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
private void checkPossibleMissNotify(Record r, int phoneId) {
int events = r.events;
@@ -1813,9 +1788,7 @@
log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = "
+ mCellInfo.get(phoneId));
}
- if (checkLocationAccess(r)) {
- r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
- }
+ r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1863,9 +1836,7 @@
try {
if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = "
+ mCellLocation[phoneId]);
- if (checkLocationAccess(r)) {
- r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId]));
- }
+ r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId]));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 48b5a58..0baa3d0 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -25,7 +25,7 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
-import android.hardware.vibrator.V1_0.Constants.EffectStrength;
+import android.hardware.vibrator.V1_0.EffectStrength;
import android.icu.text.DateFormat;
import android.media.AudioManager;
import android.os.PowerManager.ServiceType;
@@ -55,6 +55,7 @@
import android.view.InputDevice;
import android.media.AudioAttributes;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
@@ -111,6 +112,7 @@
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ @GuardedBy("mLock")
private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
@@ -435,45 +437,45 @@
// If our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
- if (effect instanceof VibrationEffect.OneShot
- && mCurrentVibration != null
- && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
- VibrationEffect.OneShot currentOneShot =
- (VibrationEffect.OneShot) mCurrentVibration.effect;
- if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
- && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ synchronized (mLock) {
+ if (effect instanceof VibrationEffect.OneShot
+ && mCurrentVibration != null
+ && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+ VibrationEffect.OneShot currentOneShot =
+ (VibrationEffect.OneShot) mCurrentVibration.effect;
+ if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
+ && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
+ }
+ return;
+ }
+ }
+
+ // If the current vibration is repeating and the incoming one is non-repeating, then
+ // ignore the non-repeating vibration. This is so that we don't cancel vibrations that
+ // are meant to grab the attention of the user, like ringtones and alarms, in favor of
+ // one-shot vibrations that are likely quite short.
+ if (!isRepeatingVibration(effect)
+ && mCurrentVibration != null
+ && isRepeatingVibration(mCurrentVibration.effect)) {
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
+ Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
}
return;
}
- }
- // If the current vibration is repeating and the incoming one is non-repeating, then ignore
- // the non-repeating vibration. This is so that we don't cancel vibrations that are meant
- // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
- // vibrations that are likely quite short.
- if (!isRepeatingVibration(effect)
- && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.effect)) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
- }
- return;
- }
-
- Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
- linkVibration(vib);
-
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
+ Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+ linkVibration(vib);
+ long ident = Binder.clearCallingIdentity();
+ try {
doCancelVibrateLocked();
startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
}
@@ -516,6 +518,7 @@
}
};
+ @GuardedBy("mLock")
private void doCancelVibrateLocked() {
mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
@@ -538,6 +541,7 @@
}
}
+ @GuardedBy("mLock")
private void startVibrationLocked(final Vibration vib) {
if (!isAllowedToVibrateLocked(vib)) {
return;
@@ -568,6 +572,7 @@
startVibrationInnerLocked(vib);
}
+ @GuardedBy("mLock")
private void startVibrationInnerLocked(Vibration vib) {
mCurrentVibration = vib;
if (vib.effect instanceof VibrationEffect.OneShot) {
@@ -701,6 +706,7 @@
return mode;
}
+ @GuardedBy("mLock")
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
@@ -880,6 +886,7 @@
}
}
+ @GuardedBy("mLock")
private long doVibratorPrebakedEffectLocked(Vibration vib) {
final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
final boolean usingInputDeviceVibrators;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7d4c1..230f69d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -84,6 +84,7 @@
import android.util.EventLog;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.StatsLog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -1042,20 +1043,14 @@
throw new SecurityException("Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground services");
default:
- try {
- if (AppGlobals.getPackageManager().checkPermission(
- android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Instant app " + r.appInfo.packageName
- + " does not have permission to create foreground"
- + "services");
- }
- } catch (RemoteException e) {
- throw new SecurityException("Failed to check instant app permission." ,
- e);
- }
+ mAm.enforcePermission(
+ android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
+ r.app.pid, r.appInfo.uid, "startForeground");
}
+ } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
+ mAm.enforcePermission(
+ android.Manifest.permission.FOREGROUND_SERVICE,
+ r.app.pid, r.appInfo.uid, "startForeground");
}
if (r.fgRequired) {
if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
@@ -1094,6 +1089,8 @@
active.mNumActive++;
}
r.isForeground = true;
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.userId, r.shortName,
+ StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER);
}
r.postNotification();
if (r.app != null) {
@@ -1109,6 +1106,8 @@
decActiveForegroundAppLocked(smap, r);
}
r.isForeground = false;
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.userId, r.shortName,
+ StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
if (r.app != null) {
mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app, true);
@@ -2533,7 +2532,10 @@
cancelForegroundNotificationLocked(r);
if (r.isForeground) {
decActiveForegroundAppLocked(smap, r);
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.userId, r.shortName,
+ StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
}
+
r.isForeground = false;
r.foregroundId = 0;
r.foregroundNoti = null;
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 220014f..608352b 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -657,6 +657,26 @@
return false;
}
+ void remove() {
+ final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
+ while (getChildCount() > 0) {
+ final ActivityStack stack = getChildAt(0);
+ if (destroyContentOnRemoval) {
+ mSupervisor.moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
+ false /* onTop */);
+ stack.finishAllActivitiesLocked(true /* immediately */);
+ } else {
+ // Moving all tasks to fullscreen stack, because it's guaranteed to be
+ // a valid launch stack for all activities. This way the task history from
+ // external display will be preserved on primary after move.
+ mSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+ }
+ }
+
+ mWindowContainerController.removeContainer();
+ mWindowContainerController = null;
+ }
+
/** Update and get all UIDs that are present on the display and have access to it. */
IntArray getPresentUIDs() {
mDisplayAccessUIDs.clear();
@@ -666,7 +686,7 @@
return mDisplayAccessUIDs;
}
- boolean shouldDestroyContentOnRemove() {
+ private boolean shouldDestroyContentOnRemove() {
return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 99f36d0..e44223b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1854,7 +1854,6 @@
static final int REPORT_MEM_USAGE_MSG = 33;
static final int IMMERSIVE_MODE_LOCK_MSG = 37;
static final int PERSIST_URI_GRANTS_MSG = 38;
- static final int REQUEST_ALL_PSS_MSG = 39;
static final int UPDATE_TIME_PREFERENCE_MSG = 41;
static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
static final int FINISH_BOOTING_MSG = 45;
@@ -2320,12 +2319,6 @@
writeGrantedUriPermissions();
break;
}
- case REQUEST_ALL_PSS_MSG: {
- synchronized (ActivityManagerService.this) {
- requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
- }
- break;
- }
case UPDATE_TIME_PREFERENCE_MSG: {
// The user's time format preference might have changed.
// For convenience we re-use the Intent extra values.
@@ -2615,11 +2608,17 @@
procState = proc.pssProcState;
statType = proc.pssStatType;
lastPssTime = proc.lastPssTime;
+ long now = SystemClock.uptimeMillis();
if (proc.thread != null && procState == proc.setProcState
&& (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE)
- < SystemClock.uptimeMillis()) {
+ < now) {
pid = proc.pid;
} else {
+ ProcessList.abortNextPssTime(proc.procStateMemTracker);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid +
+ ": still need " +
+ (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE-now) +
+ "ms until safe");
proc = null;
pid = 0;
}
@@ -3005,6 +3004,16 @@
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
+
+ // bind background thread to little cores
+ // this is expected to fail inside of framework tests because apps can't touch cpusets directly
+ try {
+ Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
+ Process.THREAD_GROUP_BG_NONINTERACTIVE);
+ } catch (Exception e) {
+ Slog.w(TAG, "Setting background thread cpuset failed");
+ }
+
}
protected ActivityStackSupervisor createStackSupervisor() {
@@ -3283,6 +3292,7 @@
* Update AMS states when an activity is resumed. This should only be called by
* {@link ActivityStack#setResumedActivityLocked} when an activity is resumed.
*/
+ @GuardedBy("this")
void setResumedActivityUncheckLocked(ActivityRecord r, String reason) {
final TaskRecord task = r.getTask();
if (task.isActivityTypeStandard()) {
@@ -3808,6 +3818,7 @@
info.className = entryPoint;
info.packageName = "android";
info.seInfoUser = SELinuxUtil.COMPLETE_STR;
+ info.targetSdkVersion = Build.VERSION.SDK_INT;
ProcessRecord proc = startProcessLocked(processName, info /* info */,
false /* knownToBeDead */, 0 /* intentFlags */, "" /* hostingType */,
null /* hostingName */, true /* allowWhileBooting */, true /* isolated */,
@@ -3817,6 +3828,7 @@
}
}
+ @GuardedBy("this")
final ProcessRecord startProcessLocked(String processName,
ApplicationInfo info, boolean knownToBeDead, int intentFlags,
String hostingType, ComponentName hostingName, boolean allowWhileBooting,
@@ -3827,6 +3839,7 @@
null /* crashHandler */);
}
+ @GuardedBy("this")
final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName,
boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
@@ -3943,6 +3956,7 @@
return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0;
}
+ @GuardedBy("this")
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
startProcessLocked(app, hostingType, hostingNameStr, null /* abiOverride */);
@@ -3951,6 +3965,7 @@
/**
* @return {@code true} if process start is successful, false otherwise.
*/
+ @GuardedBy("this")
private final boolean startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride) {
if (app.pendingStart) {
@@ -5125,6 +5140,7 @@
.supportsLocalVoiceInteraction();
}
+ @GuardedBy("this")
void onLocalVoiceInteractionStartedLocked(IBinder activity,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
ActivityRecord activityToCallback = ActivityRecord.forTokenLocked(activity);
@@ -5623,6 +5639,7 @@
* as a result of that process going away. Clears out all connections
* to the process.
*/
+ @GuardedBy("this")
private final void handleAppDiedLocked(ProcessRecord app,
boolean restarting, boolean allowRestart) {
int pid = app.pid;
@@ -5769,10 +5786,12 @@
}
}
+ @GuardedBy("this")
final void appDiedLocked(ProcessRecord app) {
appDiedLocked(app, app.pid, app.thread, false);
}
+ @GuardedBy("this")
final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
boolean fromBinderDied) {
// First check if this ProcessRecord is actually active for the pid.
@@ -6230,6 +6249,7 @@
}
}
+ @GuardedBy("this")
final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) {
if (!mLaunchWarningShown) {
mLaunchWarningShown = true;
@@ -6656,6 +6676,7 @@
}
}
+ @GuardedBy("this")
void closeSystemDialogsLocked(String reason) {
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -6762,11 +6783,13 @@
}
}
+ @GuardedBy("this")
private void forceStopPackageLocked(final String packageName, int uid, String reason) {
forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
false, true, false, false, UserHandle.getUserId(uid), reason);
}
+ @GuardedBy("this")
private void finishForceStopPackageLocked(final String packageName, int uid) {
Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
Uri.fromParts("package", packageName, null));
@@ -6782,6 +6805,7 @@
}
+ @GuardedBy("this")
private final boolean killPackageProcessesLocked(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
boolean doit, boolean evenPersistent, String reason) {
@@ -6953,6 +6977,7 @@
return didSomething;
}
+ @GuardedBy("this")
final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
boolean evenPersistent, boolean uninstalling, int userId, String reason) {
@@ -7164,6 +7189,7 @@
}
}
+ @GuardedBy("this")
boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, String reason) {
final String name = app.processName;
@@ -7218,6 +7244,7 @@
return needRestart;
}
+ @GuardedBy("this")
private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
cleanupAppInLaunchingProvidersLocked(app, true);
removeProcessLocked(app, false, true, "timeout publishing content providers");
@@ -7278,6 +7305,7 @@
}
}
+ @GuardedBy("this")
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid, int callingUid, long startSeq) {
@@ -8861,6 +8889,20 @@
/**
* This can be called with or without the global lock held.
*/
+ void enforcePermission(String permission, int pid, int uid, String func) {
+ if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ String msg = "Permission Denial: " + func + " from pid=" + pid + ", uid=" + uid
+ + " requires " + permission;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ /**
+ * This can be called with or without the global lock held.
+ */
void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
enforceCallingPermission(permission, func);
@@ -9131,6 +9173,7 @@
grantEphemeralAccess(userId, intent, targetAppId, ephemeralAppId);
}
+ @GuardedBy("this")
private UriPermission findUriPermissionLocked(int targetUid, GrantUri grantUri) {
final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris != null) {
@@ -9139,6 +9182,7 @@
return null;
}
+ @GuardedBy("this")
private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,
String targetPkg, int targetUid, GrantUri grantUri) {
ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
@@ -9156,6 +9200,7 @@
return perm;
}
+ @GuardedBy("this")
private final boolean checkUriPermissionLocked(GrantUri grantUri, int uid,
final int modeFlags) {
final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
@@ -9226,6 +9271,7 @@
* If you already know the uid of the target, you can supply it in
* lastTargetUid else set that to -1.
*/
+ @GuardedBy("this")
int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri,
final int modeFlags, int lastTargetUid) {
if (!Intent.isAccessUriMode(modeFlags)) {
@@ -9388,6 +9434,7 @@
}
}
+ @GuardedBy("this")
void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, GrantUri grantUri,
final int modeFlags, UriPermissionOwner owner) {
if (!Intent.isAccessUriMode(modeFlags)) {
@@ -9417,6 +9464,7 @@
perm.grantModes(modeFlags, owner);
}
+ @GuardedBy("this")
void grantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri,
final int modeFlags, UriPermissionOwner owner, int targetUserId) {
if (targetPkg == null) {
@@ -9468,6 +9516,7 @@
/**
* Like checkGrantUriPermissionLocked, but takes an Intent.
*/
+ @GuardedBy("this")
NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid,
String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
@@ -9554,6 +9603,7 @@
/**
* Like grantUriPermissionUncheckedLocked, but takes an Intent.
*/
+ @GuardedBy("this")
void grantUriPermissionUncheckedFromIntentLocked(NeededUriGrants needed,
UriPermissionOwner owner) {
if (needed != null) {
@@ -9565,6 +9615,7 @@
}
}
+ @GuardedBy("this")
void grantUriPermissionFromIntentLocked(int callingUid,
String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId) {
NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg,
@@ -9609,6 +9660,7 @@
}
}
+ @GuardedBy("this")
void removeUriPermissionIfNeededLocked(UriPermission perm) {
if (perm.modeFlags == 0) {
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
@@ -9625,6 +9677,7 @@
}
}
+ @GuardedBy("this")
private void revokeUriPermissionLocked(String targetPackage, int callingUid, GrantUri grantUri,
final int modeFlags) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
@@ -9759,6 +9812,7 @@
* @param targetOnly When {@code true}, only remove permissions where the app is the target,
* not source.
*/
+ @GuardedBy("this")
private void removeUriPermissionsForPackageLocked(
String packageName, int userHandle, boolean persistable, boolean targetOnly) {
if (userHandle == UserHandle.USER_ALL && packageName == null) {
@@ -9945,6 +9999,7 @@
}
}
+ @GuardedBy("this")
private void readGrantedUriPermissionsLocked() {
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "readGrantedUriPermissions()");
@@ -10107,6 +10162,7 @@
*
* @return if any mutations occured that require persisting.
*/
+ @GuardedBy("this")
private boolean maybePrunePersistedUriGrantsLocked(int uid) {
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
if (perms == null) return false;
@@ -11316,8 +11372,11 @@
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
- startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId),
- true /* isSystemCaller */);
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+
+ // When starting lock task mode the stack must be in front and focused
+ task.getStack().moveToFront("startSystemLockTaskMode");
+ startLockTaskModeLocked(task, true /* isSystemCaller */);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -12604,6 +12663,7 @@
// GLOBAL MANAGEMENT
// =========================================================
+ @GuardedBy("this")
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
boolean isolated, int isolatedUid) {
String proc = customProcess != null ? customProcess : info.processName;
@@ -12648,6 +12708,9 @@
if (!mBooted && !mBooting
&& userId == UserHandle.USER_SYSTEM
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
+ // The system process is initialized to SCHED_GROUP_DEFAULT in init.rc.
+ r.curSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ r.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
r.persistent = true;
r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
}
@@ -12690,6 +12753,7 @@
}
}
+ @GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
String abiOverride) {
ProcessRecord app;
@@ -12806,6 +12870,7 @@
}
}
+ @GuardedBy("this")
void finishRunningVoiceLocked() {
if (mRunningVoice != null) {
mRunningVoice = null;
@@ -12821,6 +12886,7 @@
}
}
+ @GuardedBy("this")
void updateSleepIfNeededLocked() {
final boolean shouldSleep = !mStackSupervisor.hasAwakeDisplay();
final boolean wasSleeping = mSleeping;
@@ -12925,6 +12991,7 @@
Binder.restoreCallingIdentity(origId);
}
+ @GuardedBy("this")
void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) {
Slog.d(TAG, "<<< startRunningVoiceLocked()");
mVoiceWakeLock.setWorkSource(new WorkSource(targetUid));
@@ -14477,9 +14544,6 @@
mTestPssMode, isSleepingLocked(), now);
}
}
-
- mHandler.removeMessages(REQUEST_ALL_PSS_MSG);
- mHandler.sendEmptyMessageDelayed(REQUEST_ALL_PSS_MSG, 2*60*1000);
}
}
@@ -14786,8 +14850,6 @@
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
BinderInternal.nSetBinderProxyCountEnabled(true);
- //STOPSHIP: Temporary BinderProxy Threshold for b/71353150
- BinderInternal.nSetBinderProxyCountWatermarks(1500, 1200);
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
@@ -16165,6 +16227,7 @@
return false;
}
+ @GuardedBy("this")
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
boolean needSep = false;
@@ -16618,6 +16681,7 @@
pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck);
}
+ @GuardedBy("this")
void writeProcessesToProtoLocked(ProtoOutputStream proto, String dumpPackage) {
int numPers = 0;
@@ -17445,6 +17509,7 @@
}
}
+ @GuardedBy("this")
void dumpPermissionsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
@@ -19517,6 +19582,7 @@
* @return Returns true if the given process has been restarted, so the
* app that was passed in must remain on the process lists.
*/
+ @GuardedBy("this")
private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
if (index >= 0) {
@@ -19526,6 +19592,7 @@
mProcessesToGc.remove(app);
mPendingPssProcesses.remove(app);
+ ProcessList.abortNextPssTime(app.procStateMemTracker);
// Dismiss any open dialogs.
if (app.crashDialog != null && !app.forceCrashReport) {
@@ -20605,6 +20672,7 @@
}
}
+ @GuardedBy("this")
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
@@ -21595,6 +21663,7 @@
}
}
+ @GuardedBy("this")
void finishInstrumentationLocked(ProcessRecord app, int resultCode, Bundle results) {
if (app.instr == null) {
Slog.w(TAG, "finishInstrumentation called on non-instrumented: " + app);
@@ -21879,7 +21948,7 @@
}
try {
if (values != null) {
- changes = updateGlobalConfiguration(values, initLocale, persistent, userId,
+ changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId,
deferResume);
}
@@ -21897,8 +21966,16 @@
return kept;
}
+ /**
+ * Returns true if this configuration change is interesting enough to send an
+ * {@link Intent#ACTION_SPLIT_CONFIGURATION_CHANGED} broadcast.
+ */
+ private static boolean isSplitConfigurationChange(int configDiff) {
+ return (configDiff & (ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_DENSITY)) != 0;
+ }
+
/** Update default (global) configuration and notify listeners about changes. */
- private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
+ private int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
mTempConfig.setTo(getGlobalConfiguration());
final int changes = mTempConfig.updateFrom(values);
@@ -22001,6 +22078,19 @@
UserHandle.USER_ALL);
}
+ // Send a broadcast to PackageInstallers if the configuration change is interesting
+ // for the purposes of installing additional splits.
+ if (!initLocale && isSplitConfigurationChange(changes)) {
+ intent = new Intent(Intent.ACTION_SPLIT_CONFIGURATION_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+
+ // Typically only app stores will have this permission.
+ String[] permissions = new String[] { android.Manifest.permission.INSTALL_PACKAGES };
+ broadcastIntentLocked(null, null, intent, null, null, 0, null, null, permissions,
+ OP_NONE, null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
+ }
+
// Override configuration of the default display duplicates global config, so we need to
// update it also. This will also notify WindowManager about changes.
performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
@@ -22073,7 +22163,7 @@
// Override configuration of the default display duplicates global config, so
// we're calling global config update instead for default display. It will also
// apply the correct override config.
- changes = updateGlobalConfiguration(values, false /* initLocale */,
+ changes = updateGlobalConfigurationLocked(values, false /* initLocale */,
false /* persistent */, UserHandle.USER_NULL /* userId */, deferResume);
} else {
changes = performDisplayOverrideConfigUpdate(values, deferResume, displayId);
@@ -23290,9 +23380,9 @@
/**
* Schedule PSS collection of a process.
*/
- void requestPssLocked(ProcessRecord proc, int procState) {
+ boolean requestPssLocked(ProcessRecord proc, int procState) {
if (mPendingPssProcesses.contains(proc)) {
- return;
+ return false;
}
if (mPendingPssProcesses.size() == 0) {
mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
@@ -23301,6 +23391,7 @@
proc.pssProcState = procState;
proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE;
mPendingPssProcesses.add(proc);
+ return true;
}
/**
@@ -23317,6 +23408,9 @@
if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered);
mLastFullPssTime = now;
mFullPssPending = true;
+ for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) {
+ ProcessList.abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker);;
+ }
mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
mPendingPssProcesses.clear();
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
@@ -23325,7 +23419,9 @@
|| app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
continue;
}
- if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
+ if (memLowered || (always && now >
+ app.lastStateTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE)
+ || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL
: ProcessStats.ADD_PSS_INTERNAL_ALL_MEM;
@@ -23334,7 +23430,9 @@
mPendingPssProcesses.add(app);
}
}
- mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+ if (!mBgHandler.hasMessages(COLLECT_PSS_BG_MSG)) {
+ mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+ }
}
public void setTestPssMode(boolean enabled) {
@@ -23692,7 +23790,7 @@
// Experimental code to more aggressively collect pss while
// running test... the problem is that this tends to collect
// the data right when a process is transitioning between process
- // states, which well tend to give noisy data.
+ // states, which will tend to give noisy data.
long start = SystemClock.uptimeMillis();
long startTime = SystemClock.currentThreadTimeMillis();
long pss = Debug.getPss(app.pid, mTmpLong, null);
@@ -23715,9 +23813,10 @@
if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
&& now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
mTestPssMode)))) {
- requestPssLocked(app, app.setProcState);
- app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState,
- app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now);
+ if (requestPssLocked(app, app.setProcState)) {
+ app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState,
+ app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now);
+ }
} else if (false && DEBUG_PSS) Slog.d(TAG_PSS,
"Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
}
@@ -23984,6 +24083,7 @@
return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}
+ @GuardedBy("this")
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
boolean oomAdj) {
if (isForeground != proc.foregroundServices) {
@@ -24053,6 +24153,7 @@
* if necessary, or skip.
* @return whether updateOomAdjLocked(app) was successful.
*/
+ @GuardedBy("this")
final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) {
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
@@ -24077,6 +24178,7 @@
return success;
}
+ @GuardedBy("this")
final void updateOomAdjLocked() {
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
@@ -24797,6 +24899,7 @@
/**
* Whitelists {@code targetUid} to temporarily bypass Power Save mode.
*/
+ @GuardedBy("this")
void tempWhitelistForPendingIntentLocked(int callerPid, int callerUid, int targetUid,
long duration, String tag) {
if (DEBUG_WHITELISTS) {
@@ -24829,6 +24932,7 @@
/**
* Whitelists {@code targetUid} to temporarily bypass Power Save mode.
*/
+ @GuardedBy("this")
void tempWhitelistUidLocked(int targetUid, long duration, String tag) {
mPendingTempWhitelist.put(targetUid, new PendingTempWhitelist(targetUid, duration, tag));
setUidTempWhitelistStateLocked(targetUid, true);
@@ -24868,6 +24972,7 @@
}
}
+ @GuardedBy("this")
final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
boolean changed = false;
for (int i=mActiveUids.size()-1; i>=0; i--) {
@@ -24882,6 +24987,7 @@
}
}
+ @GuardedBy("this")
final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
boolean changed = false;
final UidRecord uidRec = mActiveUids.get(uid);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index cae0d2b..9838851 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -133,11 +133,16 @@
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.app.servertransaction.ActivityRelaunchItem;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.MoveToDisplayItem;
import android.app.servertransaction.MultiWindowModeChangeItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PipModeChangeItem;
+import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.WindowVisibilityItem;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.content.ComponentName;
@@ -375,7 +380,8 @@
}
String getLifecycleDescription(String reason) {
- return "packageName=" + packageName + ", state=" + state + ", reason=" + reason;
+ return "component:" + intent.getComponent().flattenToShortString() + ", state=" + state
+ + ", reason=" + reason + ", time=" + System.currentTimeMillis();
}
void dump(PrintWriter pw, String prefix) {
@@ -2370,6 +2376,15 @@
setLastReportedConfiguration(service.getGlobalConfiguration(), newMergedOverrideConfig);
+ if (state == INITIALIZING) {
+ // No need to relaunch or schedule new config for activity that hasn't been launched
+ // yet. We do, however, return after applying the config to activity record, so that
+ // it will use it for launch transaction.
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Skipping config check for initializing activity: " + this);
+ return true;
+ }
+
if (changes == 0 && !forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration no differences in " + this);
@@ -2559,12 +2574,23 @@
+ " callers=" + Debug.getCallers(6));
forceNewConfig = false;
mStackSupervisor.activityRelaunchingLocked(this);
- app.thread.scheduleRelaunchActivity(appToken, pendingResults, pendingNewIntents,
- configChangeFlags, !andResume,
- new Configuration(service.getGlobalConfiguration()),
- new Configuration(getMergedOverrideConfiguration()), preserveWindow);
+ final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
+ pendingNewIntents, configChangeFlags,
+ new MergedConfiguration(service.getGlobalConfiguration(),
+ getMergedOverrideConfiguration()),
+ preserveWindow);
+ final ActivityLifecycleItem lifecycleItem;
+ if (andResume) {
+ lifecycleItem = ResumeActivityItem.obtain(service.isNextTransitionForward());
+ } else {
+ lifecycleItem = PauseActivityItem.obtain();
+ }
+ final ClientTransaction transaction = ClientTransaction.obtain(app.thread, appToken);
+ transaction.addCallback(callbackItem);
+ transaction.setLifecycleStateRequest(lifecycleItem);
+ service.mLifecycleManager.scheduleTransaction(transaction);
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
- // pass in 'andResume' if this activity is currently resumed, which implies we aren't
+ // request resume if this activity is currently resumed, which implies we aren't
// sleeping.
} catch (RemoteException e) {
if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ab2dc36..055a1aa 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -138,6 +138,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BatteryStatsImpl;
@@ -2211,6 +2212,7 @@
* Use {@link ActivityStackSupervisor#resumeFocusedStackTopActivityLocked} to resume the
* right activity for the current system state.
*/
+ @GuardedBy("mService")
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
if (mStackSupervisor.inResumeTopActivity) {
// Don't even start recursing.
@@ -2250,6 +2252,7 @@
mStackSupervisor.mRecentTasks.add(r.getTask());
}
+ @GuardedBy("mService")
private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
if (!mService.mBooting && !mService.mBooted) {
// Not ready yet!
@@ -4214,7 +4217,8 @@
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
DestroyActivityItem.obtain(r.finishing, r.configChangeFlags)
- .setDescription(r.getLifecycleDescription("destroyActivityLocked")));
+ .setDescription(
+ r.getLifecycleDescription("destroyActivityLocked:" + reason)));
} catch (Exception e) {
// We can just ignore exceptions here... if the process
// has crashed, our death notification will clean things
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 77f6d44..4928e90 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -166,6 +166,7 @@
import android.view.Display;
import android.view.RemoteAnimationAdapter;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -1266,7 +1267,7 @@
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
int modifiedFlags = flags
| PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS;
- if (intent.isBrowsableWebIntent()
+ if (intent.isWebIntent()
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
modifiedFlags |= PackageManager.MATCH_INSTANT;
}
@@ -1508,6 +1509,8 @@
Slog.w(TAG, "Activity " + r + " being launched, but already in LRU list");
}
+ // TODO(lifecycler): Resume or pause requests are done as part of launch transaction,
+ // so updating the state should be done accordingly.
if (andResume && readyToResume()) {
// As part of the process of launching, ActivityThread also performs
// a resume.
@@ -1858,6 +1861,7 @@
* Called when the frontmost task is idle.
* @return the state of mService.mBooting before this was called.
*/
+ @GuardedBy("mService")
private boolean checkFinishBootingLocked() {
final boolean booting = mService.mBooting;
boolean enableScreen = false;
@@ -1873,6 +1877,7 @@
}
// Checked.
+ @GuardedBy("mService")
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
boolean processPausingActivities, Configuration config) {
if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
@@ -4087,25 +4092,12 @@
if (activityDisplay == null) {
return;
}
- final boolean destroyContentOnRemoval
- = activityDisplay.shouldDestroyContentOnRemove();
- while (activityDisplay.getChildCount() > 0) {
- final ActivityStack stack = activityDisplay.getChildAt(0);
- if (destroyContentOnRemoval) {
- moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, false /* onTop */);
- stack.finishAllActivitiesLocked(true /* immediately */);
- } else {
- // Moving all tasks to fullscreen stack, because it's guaranteed to be
- // a valid launch stack for all activities. This way the task history from
- // external display will be preserved on primary after move.
- moveTasksToFullscreenStackLocked(stack, true /* onTop */);
- }
- }
+
+ activityDisplay.remove();
releaseSleepTokens(activityDisplay);
mActivityDisplays.remove(displayId);
- mWindowManager.onDisplayRemoved(displayId);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b061ba4..055b89b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -790,7 +790,7 @@
// Instead, launch the ephemeral installer. Once the installer is finished, it
// starts either the intent we resolved here [on install error] or the ephemeral
// app [on install success].
- if (rInfo != null && rInfo.isInstantAppAvailable) {
+ if (rInfo != null && rInfo.auxiliaryInfo != null) {
intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent,
callingPackage, verificationBundle, resolvedType, userId);
resolvedType = null;
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 927b72c..ef82f36 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -182,6 +182,7 @@
mExecutorService.shutdownNow();
}
+ @GuardedBy("this")
private Future<?> scheduleSyncLocked(String reason, int flags) {
if (mExecutorService.isShutdown()) {
return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
@@ -248,6 +249,7 @@
}
};
+ @GuardedBy("mWorkerLock")
private void updateExternalStatsLocked(final String reason, int updateFlags) {
// We will request data from external processes asynchronously, and wait on a timeout.
SynchronousResultReceiver wifiReceiver = null;
@@ -372,6 +374,7 @@
return null;
}
+ @GuardedBy("mWorkerLock")
private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
final long lastScanMs = mLastInfo.mControllerScanTimeMs;
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
index 9c2ee87..71fd71b8 100644
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -41,7 +41,7 @@
// {Settings.Global.SETTING_NAME, "system_property_name"},
{Settings.Global.SYS_VDSO, "sys.vdso"},
{Settings.Global.FPS_DEVISOR, ThreadedRenderer.DEBUG_FPS_DIVISOR},
- {Settings.Global.UID_CPUPOWER, "uid.cpupower"},
+ {Settings.Global.DISPLAY_PANEL_LPM, "sys.display_panel_lpm"},
};
@@ -93,7 +93,7 @@
}
try {
systemPropertiesSet(key, value);
- } catch (IllegalArgumentException e) {
+ } catch (Exception e) {
Slog.e(TAG, "Unable to set property " + key + " value '" + value + "'", e);
}
}
diff --git a/services/core/java/com/android/server/am/PersistentConnection.java b/services/core/java/com/android/server/am/PersistentConnection.java
index 52eaca1..c5edb26 100644
--- a/services/core/java/com/android/server/am/PersistentConnection.java
+++ b/services/core/java/com/android/server/am/PersistentConnection.java
@@ -220,6 +220,7 @@
}
}
+ @GuardedBy("mLock")
public final void bindInnerLocked(boolean resetBackoff) {
unscheduleRebindLocked();
@@ -260,6 +261,7 @@
}
}
+ @GuardedBy("mLock")
private void cleanUpConnectionLocked() {
mIsConnected = false;
mService = null;
@@ -276,6 +278,7 @@
}
}
+ @GuardedBy("mLock")
private final void unbindLocked() {
unscheduleRebindLocked();
@@ -289,11 +292,13 @@
cleanUpConnectionLocked();
}
+ @GuardedBy("mLock")
void unscheduleRebindLocked() {
injectRemoveCallbacks(mBindForBackoffRunnable);
mRebindScheduled = false;
}
+ @GuardedBy("mLock")
void scheduleRebindLocked() {
unbindLocked();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 08ee237..bf7aef9 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -501,19 +501,19 @@
private static final int PSS_FIRST_CACHED_INTERVAL = 20*1000;
// The amount of time until PSS when an important process stays in the same state.
- private static final int PSS_SAME_PERSISTENT_INTERVAL = 20*60*1000;
+ private static final int PSS_SAME_PERSISTENT_INTERVAL = 10*60*1000;
// The amount of time until PSS when the top process stays in the same state.
- private static final int PSS_SAME_TOP_INTERVAL = 5*60*1000;
+ private static final int PSS_SAME_TOP_INTERVAL = 1*60*1000;
// The amount of time until PSS when an important process stays in the same state.
- private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000;
+ private static final int PSS_SAME_IMPORTANT_INTERVAL = 10*60*1000;
// The amount of time until PSS when a service process stays in the same state.
- private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000;
+ private static final int PSS_SAME_SERVICE_INTERVAL = 5*60*1000;
// The amount of time until PSS when a cached process stays in the same state.
- private static final int PSS_SAME_CACHED_INTERVAL = 20*60*1000;
+ private static final int PSS_SAME_CACHED_INTERVAL = 10*60*1000;
// The amount of time until PSS when a persistent process first appears.
private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000;
@@ -622,16 +622,17 @@
public static final class ProcStateMemTracker {
final int[] mHighestMem = new int[PROC_MEM_NUM];
+ final float[] mScalingFactor = new float[PROC_MEM_NUM];
int mTotalHighestMem = PROC_MEM_CACHED;
- float mCurFactor = 1.0f;
int mPendingMemState;
int mPendingHighestMemState;
- boolean mPendingSame;
+ float mPendingScalingFactor;
public ProcStateMemTracker() {
for (int i = PROC_MEM_PERSISTENT; i < PROC_MEM_NUM; i++) {
mHighestMem[i] = PROC_MEM_NUM;
+ mScalingFactor[i] = 1.0f;
}
mPendingMemState = -1;
}
@@ -639,16 +640,22 @@
public void dumpLine(PrintWriter pw) {
pw.print("best=");
pw.print(mTotalHighestMem);
- pw.print(" ");
- pw.print(mCurFactor);
- pw.print("x (");
+ pw.print(" (");
+ boolean needSep = false;
for (int i = 0; i < PROC_MEM_NUM; i++) {
- if (i != 0) {
- pw.print(", ");
+ if (mHighestMem[i] < PROC_MEM_NUM) {
+ if (needSep) {
+ pw.print(", ");
+ needSep = false;
+ }
+ pw.print(i);
+ pw.print("=");
+ pw.print(mHighestMem[i]);
+ pw.print(" ");
+ pw.print(mScalingFactor[i]);
+ pw.print("x");
+ needSep = true;
}
- pw.print(i);
- pw.print("=");
- pw.print(mHighestMem[i]);
}
pw.print(")");
if (mPendingMemState >= 0) {
@@ -656,8 +663,9 @@
pw.print(mPendingMemState);
pw.print(" highest=");
pw.print(mPendingHighestMemState);
- pw.print(" same=");
- pw.print(mPendingSame);
+ pw.print(" ");
+ pw.print(mPendingScalingFactor);
+ pw.print("x");
}
pw.println();
}
@@ -674,12 +682,8 @@
public static void commitNextPssTime(ProcStateMemTracker tracker) {
if (tracker.mPendingMemState >= 0) {
tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState;
+ tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor;
tracker.mTotalHighestMem = tracker.mPendingHighestMemState;
- if (tracker.mPendingSame) {
- tracker.mCurFactor *= 1.5f;
- } else {
- tracker.mCurFactor = 1;
- }
tracker.mPendingMemState = -1;
}
}
@@ -691,6 +695,7 @@
public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test,
boolean sleeping, long now) {
boolean first;
+ float scalingFactor;
final int memState = sProcStateToProcMem[procState];
if (tracker != null) {
final int highestMemState = memState < tracker.mTotalHighestMem
@@ -698,9 +703,15 @@
first = highestMemState < tracker.mHighestMem[memState];
tracker.mPendingMemState = memState;
tracker.mPendingHighestMemState = highestMemState;
- tracker.mPendingSame = !first;
+ if (first) {
+ tracker.mPendingScalingFactor = scalingFactor = 1.0f;
+ } else {
+ scalingFactor = tracker.mScalingFactor[memState];
+ tracker.mPendingScalingFactor = scalingFactor * 1.5f;
+ }
} else {
first = true;
+ scalingFactor = 1.0f;
}
final long[] table = test
? (first
@@ -709,8 +720,7 @@
: (first
? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes)
: (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes));
- long delay = (long)(table[memState] * (tracker != null && !first
- ? tracker.mCurFactor : 1.0f));
+ long delay = (long)(table[memState] * scalingFactor);
if (delay > PSS_MAX_INTERVAL) {
delay = PSS_MAX_INTERVAL;
}
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 8bf320e..c10d81b 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -135,6 +135,7 @@
return mMemFactorLowered;
}
+ @GuardedBy("mAm")
public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) {
mMemFactorLowered = memFactor < mLastMemOnlyState;
mLastMemOnlyState = memFactor;
@@ -237,6 +238,7 @@
if (commit) {
mProcessStats.resetSafely();
updateFile();
+ mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
}
mLastWriteTime = SystemClock.uptimeMillis();
totalTime = SystemClock.uptimeMillis() - now;
@@ -784,12 +786,14 @@
} else if ("--reset".equals(arg)) {
synchronized (mAm) {
mProcessStats.resetSafely();
+ mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
pw.println("Process stats reset.");
quit = true;
}
} else if ("--clear".equals(arg)) {
synchronized (mAm) {
mProcessStats.resetSafely();
+ mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
ArrayList<String> files = getCommittedFiles(0, true, true);
if (files != null) {
for (int fi=0; fi<files.size(); fi++) {
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index e7b067b..db4e09f 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.WindowManager.TRANSIT_NONE;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -27,6 +28,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.os.Handler;
+import android.os.Trace;
import android.view.IRecentsAnimationRunner;
import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
import com.android.server.wm.WindowManagerService;
@@ -70,6 +72,7 @@
void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner,
ComponentName recentsComponent, int recentsUid) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
mWindowManager.deferSurfaceLayout();
try {
// Cancel the previous recents animation if necessary
@@ -124,6 +127,7 @@
mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT);
} finally {
mWindowManager.continueSurfaceLayout();
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -134,6 +138,8 @@
if (mWindowManager.getRecentsAnimationController() == null) return;
mWindowManager.inSurfaceTransaction(() -> {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
mWindowManager.deferSurfaceLayout();
try {
mWindowManager.cleanupRecentsAnimation();
@@ -167,6 +173,7 @@
mWindowManager.executeAppTransition();
} finally {
mWindowManager.continueSurfaceLayout();
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
});
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 7b0c714..d54e264 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -743,7 +743,7 @@
mInjector.stackSupervisorRemoveUser(userId);
// Remove the user if it is ephemeral.
if (getUserInfo(userId).isEphemeral()) {
- mInjector.getUserManager().removeUser(userId);
+ mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
}
// Evict the user's credential encryption key.
try {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1825db8..e95608f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -23,6 +23,7 @@
import static android.os.Process.FIRST_APPLICATION_UID;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -240,6 +241,7 @@
private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25;
private static final int MSG_INDICATE_SYSTEM_READY = 26;
private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27;
+ private static final int MSG_NOTIFY_VOL_EVENT = 28;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -283,7 +285,7 @@
private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2];
/** Maximum volume index values for audio streams */
- private static int[] MAX_STREAM_VOLUME = new int[] {
+ protected static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
7, // STREAM_RING
@@ -298,7 +300,7 @@
};
/** Minimum volume index values for audio streams */
- private static int[] MIN_STREAM_VOLUME = new int[] {
+ protected static int[] MIN_STREAM_VOLUME = new int[] {
1, // STREAM_VOICE_CALL
0, // STREAM_SYSTEM
0, // STREAM_RING
@@ -1334,11 +1336,9 @@
extVolCtlr = mExtVolumeController;
}
if (extVolCtlr != null) {
- try {
- mExtVolumeController.notifyVolumeAdjust(direction);
- } catch(RemoteException e) {
- // nothing we can do about this. Do not log error, too much potential for spam
- }
+ sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE,
+ direction, 0 /*ignored*/,
+ extVolCtlr, 0 /*delay*/);
} else {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
caller, Binder.getCallingUid());
@@ -1410,7 +1410,7 @@
Binder.getCallingUid());
}
- private void adjustStreamVolume(int streamType, int direction, int flags,
+ protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
if (mUseFixedVolume) {
return;
@@ -5054,6 +5054,15 @@
state);
}
+ private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc,
+ @AudioManager.VolumeAdjustment int direction) {
+ try {
+ apc.notifyVolumeAdjust(direction);
+ } catch(Exception e) {
+ // nothing we can do about this. Do not log error, too much potential for spam
+ }
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -5216,6 +5225,10 @@
case MSG_DYN_POLICY_MIX_STATE_UPDATE:
onDynPolicyMixStateUpdate((String) msg.obj, msg.arg1);
break;
+
+ case MSG_NOTIFY_VOL_EVENT:
+ onNotifyVolumeEvent((IAudioPolicyCallback) msg.obj, msg.arg1);
+ break;
}
}
}
@@ -7296,6 +7309,12 @@
//======================
/** */
public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (pcb == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy callback");
+ }
synchronized (mAudioPolicies) {
if (!mAudioPolicies.containsKey(pcb.asBinder())) {
throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch");
@@ -7304,6 +7323,23 @@
}
}
+ public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult,
+ IAudioPolicyCallback pcb) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (pcb == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy callback");
+ }
+ synchronized (mAudioPolicies) {
+ if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+ throw new IllegalStateException("Unregistered AudioPolicy for external focus");
+ }
+ mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult);
+ }
+ }
+
+
//======================
// misc
//======================
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index f2ef02f..99f0840 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -241,15 +241,15 @@
void release() {
+ final IBinder srcRef = mSourceRef;
+ final AudioFocusDeathHandler deathHdlr = mDeathHandler;
try {
- if (mSourceRef != null && mDeathHandler != null) {
- mSourceRef.unlinkToDeath(mDeathHandler, 0);
- mDeathHandler = null;
- mFocusDispatcher = null;
+ if (srcRef != null && deathHdlr != null) {
+ srcRef.unlinkToDeath(deathHdlr, 0);
}
- } catch (java.util.NoSuchElementException e) {
- Log.e(TAG, "FocusRequester.release() hit ", e);
- }
+ } catch (java.util.NoSuchElementException e) { }
+ mDeathHandler = null;
+ mFocusDispatcher = null;
}
@Override
@@ -424,7 +424,7 @@
int dispatchFocusChange(int focusChange) {
if (mFocusDispatcher == null) {
- if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); }
+ if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (focusChange == AudioManager.AUDIOFOCUS_NONE) {
@@ -445,12 +445,29 @@
try {
mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId);
} catch (android.os.RemoteException e) {
- Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e);
+ Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e);
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ void dispatchFocusResultFromExtPolicy(int requestResult) {
+ if (mFocusDispatcher == null) {
+ if (MediaFocusControl.DEBUG) {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy: no focus dispatcher");
+ }
+ }
+ if (DEBUG) {
+ Log.v(TAG, "dispatching result" + requestResult + " to " + mClientId);
+ }
+ try {
+ mFocusDispatcher.dispatchFocusResultFromExtPolicy(requestResult, mClientId);
+ } catch (android.os.RemoteException e) {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener"
+ + mClientId, e);
+ }
+ }
+
AudioFocusInfo toAudioFocusInfo() {
return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget);
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 9ddc52a..d023bd7 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -83,6 +83,10 @@
private boolean mRingOrCallActive = false;
+ private final Object mExtFocusChangeLock = new Object();
+ @GuardedBy("mExtFocusChangeLock")
+ private long mExtFocusChangeCounter;
+
protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) {
mContext = cntxt;
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -521,7 +525,7 @@
* @param requestResult
* @return true if the external audio focus policy (if any) is handling the focus request
*/
- boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, int requestResult,
+ boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi,
IAudioFocusDispatcher fd, IBinder cb) {
if (mFocusPolicy == null) {
return false;
@@ -530,6 +534,9 @@
Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId()
+ " dispatcher=" + fd);
}
+ synchronized (mExtFocusChangeLock) {
+ afi.setGen(mExtFocusChangeCounter++);
+ }
final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
if (existingFr != null) {
if (!existingFr.hasSameDispatcher(fd)) {
@@ -538,8 +545,7 @@
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
new FocusRequester(afi, fd, cb, hdlr, this));
}
- } else if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
- || requestResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
+ } else {
// new focus (future) focus owner to keep track of
final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb);
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
@@ -547,12 +553,25 @@
}
try {
//oneway
- mFocusPolicy.notifyAudioFocusRequest(afi, requestResult);
+ mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ return true;
} catch (RemoteException e) {
Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback "
+ mFocusPolicy.asBinder(), e);
}
- return true;
+ return false;
+ }
+
+ void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) {
+ synchronized (mExtFocusChangeLock) {
+ if (afi.getGen() > mExtFocusChangeCounter) {
+ return;
+ }
+ }
+ final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
+ if (fr != null) {
+ fr.dispatchFocusResultFromExtPolicy(requestResult);
+ }
}
/**
@@ -590,7 +609,12 @@
if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
+ final FocusRequester fr;
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
+ } else {
+ fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
+ }
if (fr == null) {
if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
@@ -710,9 +734,7 @@
boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) {
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
- final int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, result, fd, cb);
- return result;
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else {
// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
// granted right now, so the requester will be inserted in the focus stack
@@ -721,12 +743,11 @@
}
}
- // external focus policy: delay request for focus gain?
- final int resultWithExtPolicy = AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ // external focus policy?
if (notifyExtFocusPolicyFocusRequest_syncAf(
- afiForExtPolicy, resultWithExtPolicy, fd, cb)) {
+ afiForExtPolicy, fd, cb)) {
// stop handling focus request here as it is handled by external audio focus policy
- return resultWithExtPolicy;
+ return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
}
// handle the potential premature death of the new holder of the focus
diff --git a/services/core/java/com/android/server/car/CarServiceHelperService.java b/services/core/java/com/android/server/car/CarServiceHelperService.java
deleted file mode 100644
index 9392a6a..0000000
--- a/services/core/java/com/android/server/car/CarServiceHelperService.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.car;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import com.android.internal.car.ICarServiceHelper;
-import com.android.server.SystemService;
-
-/**
- * System service side companion service for CarService.
- * Starts car service and provide necessary API for CarService. Only for car product.
- */
-public class CarServiceHelperService extends SystemService {
- private static final String TAG = "CarServiceHelper";
- private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
- private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
- private IBinder mCarService;
- private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
-
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- Slog.i(TAG, "**CarService connected**");
- mCarService = iBinder;
- // Cannot depend on ICar which is defined in CarService, so handle binder call directly
- // instead.
- // void setCarServiceHelper(in IBinder helper)
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
- data.writeStrongBinder(mHelper.asBinder());
- try {
- mCarService.transact(IBinder.FIRST_CALL_TRANSACTION, // setCarServiceHelper
- data, null, Binder.FLAG_ONEWAY);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException from car service", e);
- handleCarServiceCrash();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- handleCarServiceCrash();
- }
- };
-
- public CarServiceHelperService(Context context) {
- super(context);
- }
-
- @Override
- public void onStart() {
- Intent intent = new Intent();
- intent.setPackage("com.android.car");
- intent.setAction(CAR_SERVICE_INTERFACE);
- if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM)) {
- Slog.wtf(TAG, "cannot start car service");
- }
- }
-
- private void handleCarServiceCrash() {
- //TODO define recovery bahavior
- }
-
- private class ICarServiceHelperImpl extends ICarServiceHelper.Stub {
- //TODO
- }
-}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 1ee0548..c3f020a 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1116,6 +1116,7 @@
return (pi != null) ? pi.packageName : null;
}
+ @GuardedBy("mCache")
private ArrayMap<Pair<String, Uri>, Bundle> findOrCreateCacheLocked(int userId,
String providerPackageName) {
ArrayMap<String, ArrayMap<Pair<String, Uri>, Bundle>> userCache = mCache.get(userId);
@@ -1131,6 +1132,7 @@
return packageCache;
}
+ @GuardedBy("mCache")
private void invalidateCacheLocked(int userId, String providerPackageName, Uri uri) {
ArrayMap<String, ArrayMap<Pair<String, Uri>, Bundle>> userCache = mCache.get(userId);
if (userCache == null) return;
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 51499f7..9980930 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -137,11 +137,13 @@
+ " params=" + jobParametersToString(params));
}
} else if (runtime < 10 * 1000) {
- // Job stopped too soon. WTF.
- wtf("Job " + jobId + " stopped in " + runtime + " ms: "
- + " startUptime=" + startUptime
- + " nowUptime=" + nowUptime
- + " params=" + jobParametersToString(params));
+ // This happens too in a normal case too, and it's rather too often.
+ // Disable it for now.
+// // Job stopped too soon. WTF.
+// wtf("Job " + jobId + " stopped in " + runtime + " ms: "
+// + " startUptime=" + startUptime
+// + " nowUptime=" + nowUptime
+// + " params=" + jobParametersToString(params));
}
mStartedSyncs.delete(jobId);
diff --git a/services/core/java/com/android/server/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index 75c0181..20aec7e 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -194,6 +194,7 @@
}
}
+ @GuardedBy("mLock")
private void openLogLocked(long now) {
// If we already have a log file opened and the date has't changed, just use it.
final long day = now % DateUtils.DAY_IN_MILLIS;
@@ -219,6 +220,7 @@
}
}
+ @GuardedBy("mLock")
private void closeCurrentLogLocked() {
IoUtils.closeQuietly(mLogWriter);
mLogWriter = null;
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 6b4666a..524de91 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -115,12 +115,10 @@
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
@GuardedBy("mEventsLock")
private boolean mEventsDirty;
- private final Runnable mEventsWriter = () -> writeEvents();
- private volatile boolean mWriteEventsScheduled;
+
+ private volatile boolean mWriteBrightnessTrackerStateScheduled;
private AmbientBrightnessStatsTracker mAmbientBrightnessStatsTracker;
- private final Runnable mAmbientBrightnessStatsWriter = () -> writeAmbientBrightnessStats();
- private volatile boolean mWriteBrightnessStatsScheduled;
private UserManager mUserManager;
private final Context mContext;
@@ -167,13 +165,7 @@
}
mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper());
mUserManager = mContext.getSystemService(UserManager.class);
- try {
- final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
- mCurrentUserId = focusedStack.userId;
- } catch (RemoteException e) {
- // Really shouldn't be possible.
- return;
- }
+ mCurrentUserId = ActivityManager.getCurrentUser();
mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget();
}
@@ -355,18 +347,17 @@
}
private void scheduleWriteBrightnessTrackerState() {
- if (!mWriteEventsScheduled) {
- mBgHandler.post(mEventsWriter);
- mWriteEventsScheduled = true;
- }
- if (!mWriteBrightnessStatsScheduled) {
- mBgHandler.post(mAmbientBrightnessStatsWriter);
- mWriteBrightnessStatsScheduled = true;
+ if (!mWriteBrightnessTrackerStateScheduled) {
+ mBgHandler.post(() -> {
+ mWriteBrightnessTrackerStateScheduled = false;
+ writeEvents();
+ writeAmbientBrightnessStats();
+ });
+ mWriteBrightnessTrackerStateScheduled = true;
}
}
private void writeEvents() {
- mWriteEventsScheduled = false;
synchronized (mEventsLock) {
if (!mEventsDirty) {
// Nothing to write
@@ -398,7 +389,6 @@
}
private void writeAmbientBrightnessStats() {
- mWriteBrightnessStatsScheduled = false;
final AtomicFile writeTo = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE);
if (writeTo == null) {
return;
@@ -642,6 +632,7 @@
}
}
if (mAmbientBrightnessStatsTracker != null) {
+ pw.println();
mAmbientBrightnessStatsTracker.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b27f1ec..23de592 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -51,7 +51,6 @@
import android.provider.Settings;
import android.util.MathUtils;
import android.util.Slog;
-import android.util.Spline;
import android.util.TimeUtils;
import android.view.Display;
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java b/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java
index 0976a22..b0cde2d 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java
@@ -202,6 +202,7 @@
}
}
+ @GuardedBy("this")
private void readStateSyncLocked() {
FileInputStream in;
if (!mFile.exists()) {
@@ -226,6 +227,7 @@
}
}
+ @GuardedBy("this")
private void parseStateLocked(XmlPullParser parser)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
@@ -243,6 +245,7 @@
}
}
+ @GuardedBy("this")
private void parseFingerprintsLocked(XmlPullParser parser)
throws IOException, XmlPullParserException {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1e09383..de0f298 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1299,6 +1299,7 @@
/**
* Return external input devices.
*/
+ @GuardedBy("mLock")
List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
return mSafeExternalInputs;
}
@@ -1421,6 +1422,7 @@
}
}
+ @GuardedBy("mLock")
List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3d079cc..b06dba9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1097,6 +1097,7 @@
}
}
+ @GuardedBy("mLock")
private List<HdmiDeviceInfo> getMhlDevicesLocked() {
return mMhlDevices;
}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 37b3990..4988974 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -295,10 +295,12 @@
}
/** Called externally when a job that was scheduled for execution should be cancelled. */
+ @GuardedBy("mLock")
void cancelExecutingJobLocked(int reason, String debugReason) {
doCancelLocked(reason, debugReason);
}
+ @GuardedBy("mLock")
void preemptExecutingJobLocked() {
doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
}
@@ -319,6 +321,7 @@
return mTimeoutElapsed;
}
+ @GuardedBy("mLock")
boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
String reason) {
final JobStatus executing = getRunningJobLocked();
@@ -512,6 +515,7 @@
}
}
+ @GuardedBy("mLock")
void doServiceBoundLocked() {
removeOpTimeOutLocked();
handleServiceBoundLocked();
@@ -531,6 +535,7 @@
}
}
+ @GuardedBy("mLock")
void doCallbackLocked(boolean reschedule, String reason) {
if (DEBUG) {
Slog.d(TAG, "doCallback of : " + mRunningJob
@@ -550,6 +555,7 @@
}
}
+ @GuardedBy("mLock")
void doCancelLocked(int arg1, String debugReason) {
if (mVerb == VERB_FINISHED) {
if (DEBUG) {
@@ -567,6 +573,7 @@
}
/** Start the job on the service. */
+ @GuardedBy("mLock")
private void handleServiceBoundLocked() {
if (DEBUG) {
Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked());
@@ -605,6 +612,7 @@
* _EXECUTING -> Error
* _STOPPING -> Error
*/
+ @GuardedBy("mLock")
private void handleStartedLocked(boolean workOngoing) {
switch (mVerb) {
case VERB_STARTING:
@@ -637,6 +645,7 @@
* _STARTING -> Error
* _PENDING -> Error
*/
+ @GuardedBy("mLock")
private void handleFinishedLocked(boolean reschedule, String reason) {
switch (mVerb) {
case VERB_EXECUTING:
@@ -659,6 +668,7 @@
* in the message queue.
* _ENDING -> No point in doing anything here, so we ignore.
*/
+ @GuardedBy("mLock")
private void handleCancelLocked(String reason) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
@@ -683,6 +693,7 @@
}
/** Process MSG_TIMEOUT here. */
+ @GuardedBy("mLock")
private void handleOpTimeoutLocked() {
switch (mVerb) {
case VERB_BINDING:
@@ -722,6 +733,7 @@
* Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
* VERB_STOPPING.
*/
+ @GuardedBy("mLock")
private void sendStopMessageLocked(String reason) {
removeOpTimeOutLocked();
if (mVerb != VERB_EXECUTING) {
@@ -747,6 +759,7 @@
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
* we want to clean up internally.
*/
+ @GuardedBy("mLock")
private void closeAndCleanupJobLocked(boolean reschedule, String reason) {
final JobStatus completedJob;
if (mVerb == VERB_FINISHED) {
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 373d87d..77e813e 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -92,6 +92,7 @@
mNetPolicyManager.registerListener(mNetPolicyListener);
}
+ @GuardedBy("mLock")
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (jobStatus.hasConnectivityConstraint()) {
@@ -101,6 +102,7 @@
}
}
+ @GuardedBy("mLock")
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
@@ -325,13 +327,16 @@
}
};
+ @GuardedBy("mLock")
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.print("Connectivity: connected=");
- pw.print(mConnected);
+ pw.println(mConnected);
+
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
- pw.println(":");
+ pw.println(" jobs");
+
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.valueAt(i);
if (js.shouldDump(filterUid)) {
@@ -339,11 +344,14 @@
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
+ pw.print(": ");
+ pw.print(js.getJob().getRequiredNetwork());
+ pw.println();
}
}
}
+ @GuardedBy("mLock")
@Override
public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
final long token = proto.start(fieldId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index e715724..6deff36 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2236,9 +2236,10 @@
* This happens during a normal migration case when the user currently has password.
*
* 2. credentialhash == null and credential == null
- * A new SP blob and a new SID will be created, while the user has no credentials.
+ * A new SP blob and will be created, while the user has no credentials.
* This can happens when we are activating an escrow token on a unsecured device, during
* which we want to create the SP structure with an empty user credential.
+ * This could also happen during an untrusted reset to clear password.
*
* 3. credentialhash == null and credential != null
* This is the untrusted credential reset, OR the user sets a new lockscreen password
@@ -2250,16 +2251,8 @@
String credential, int credentialType, int requestedQuality,
int userId) throws RemoteException {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
- // Load from the cache or a make a new one
- AuthenticationToken auth = mSpCache.get(userId);
- if (auth != null) {
- // If the synthetic password has been cached, we can only be in case 3., described
- // above, for an untrusted credential reset so a new SID is still needed.
- mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
- } else {
- auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
- credentialHash, credential, userId);
- }
+ final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
+ getGateKeeperService(), credentialHash, credential, userId);
onAuthTokenKnownForUser(userId, auth);
if (auth == null) {
Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
@@ -2473,36 +2466,41 @@
: "pattern"));
}
+ boolean untrustedReset = false;
if (auth != null) {
- // We are performing a trusted credential change i.e. a correct existing credential
- // is provided
- setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
- userId);
- mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
onAuthTokenKnownForUser(userId, auth);
} else if (response != null
&& response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
- // We are performing an untrusted credential change i.e. by DevicePolicyManager.
- // So provision a new SP and SID. This would invalidate existing escrow tokens.
- // Still support this for now but this flow will be removed in the next release.
+ // We are performing an untrusted credential change, by DevicePolicyManager or other
+ // internal callers that don't provide the existing credential
Slog.w(TAG, "Untrusted credential change invoked");
-
- if (mSpCache.get(userId) == null) {
- throw new IllegalStateException(
- "Untrusted credential reset not possible without cached SP");
- }
-
- initializeSyntheticPasswordLocked(null, credential, credentialType, requestedQuality,
- userId);
- synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
-
- notifyActivePasswordMetricsAvailable(credential, userId);
+ // Try to get a cached auth token, so we can keep SP unchanged.
+ auth = mSpCache.get(userId);
+ untrustedReset = true;
} else /* response == null || responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
Slog.w(TAG, "spBasedSetLockCredentialInternalLocked: " +
(response != null ? "rate limit exceeded" : "failed"));
return;
}
+
+ if (auth != null) {
+ if (untrustedReset) {
+ // Force change the current SID to mantain existing behaviour that an untrusted
+ // reset leads to a change of SID. If the untrusted reset is for clearing the
+ // current password, the nuking of the SID will be done in
+ // setLockCredentialWithAuthTokenLocked next
+ mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
+ }
+ setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
+ userId);
+ mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+ } else {
+ throw new IllegalStateException(
+ "Untrusted credential reset not possible without cached SP");
+ // Could call initializeSyntheticPasswordLocked(null, credential, credentialType,
+ // requestedQuality, userId) instead if we still allow untrusted reset that changes
+ // synthetic password. That would invalidate existing escrow tokens though.
+ }
mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 662ffc8..dee24c7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -44,6 +44,7 @@
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
+import java.security.cert.CertPath;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -176,8 +177,17 @@
return;
}
- PublicKey publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
+ PublicKey publicKey;
+ CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
recoveryAgentUid);
+ if (certPath != null) {
+ Log.d(TAG, "Using the public key in stored CertPath for syncing");
+ publicKey = certPath.getCertificates().get(0).getPublicKey();
+ } else {
+ Log.d(TAG, "Using the stored raw public key for syncing");
+ publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
+ recoveryAgentUid);
+ }
if (publicKey == null) {
Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
return;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 3a78f95..e5ff5b8 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -194,6 +194,9 @@
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
init(userId);
try {
+ // Try to see if the decryption key is still accessible before using the encryption key.
+ // The auth-bound decryption will be unrecoverable if the screen lock is disabled.
+ getDecryptKeyInternal(userId);
return getEncryptKeyInternal(userId);
} catch (UnrecoverableKeyException e) {
Log.i(TAG, String.format(Locale.US,
@@ -219,7 +222,7 @@
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
int generationId = getGenerationId(userId);
String alias = getEncryptAlias(userId, generationId);
- if (!mKeyStore.containsAlias(alias)) {
+ if (!isKeyLoaded(userId, generationId)) {
throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
}
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
@@ -268,7 +271,7 @@
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
int generationId = getGenerationId(userId);
String alias = getDecryptAlias(userId, generationId);
- if (!mKeyStore.containsAlias(alias)) {
+ if (!isKeyLoaded(userId, generationId)) {
throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
}
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
@@ -300,12 +303,12 @@
return;
}
if (generationId == -1) {
- Log.i(TAG, "Generating initial platform ID.");
+ Log.i(TAG, "Generating initial platform key generation ID.");
generationId = 1;
} else {
Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
+ "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
- // Had to generate a fresh key, bump the generation id
+ // Have to generate a fresh key, so bump the generation id
generationId++;
}
@@ -374,7 +377,7 @@
String decryptAlias = getDecryptAlias(userId, generationId);
SecretKey secretKey = generateAesKey();
- // Store Since decryption key first since it is more likely to fail.
+ // Store decryption key first since it is more likely to fail.
mKeyStore.setEntry(
decryptAlias,
new KeyStore.SecretKeyEntry(secretKey),
@@ -386,7 +389,6 @@
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setBoundToSpecificSecureUserId(userId)
.build());
-
mKeyStore.setEntry(
encryptAlias,
new KeyStore.SecretKeyEntry(secretKey),
@@ -397,11 +399,7 @@
setGenerationId(userId, generationId);
- try {
- secretKey.destroy();
- } catch (DestroyFailedException e) {
- Log.w(TAG, "Failed to destroy in-memory platform key.", e);
- }
+ // TODO: Use a reliable way to destroy the temporary secretKey in memory.
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index fda6cdf..9f82268 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -23,16 +23,15 @@
import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
import static android.security.keystore.RecoveryController.ERROR_SESSION_EXPIRED;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
-import android.Manifest;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
-
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryController;
@@ -43,6 +42,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
+import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
+import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
+import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
+import com.android.server.locksettings.recoverablekeystore.certificate.TrustedRootCert;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -52,8 +55,10 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
import java.security.UnrecoverableKeyException;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateEncodingException;
+import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
@@ -152,18 +157,67 @@
}
public void initRecoveryService(
- @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
+ @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
throws RemoteException {
checkRecoverKeyStorePermission();
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
- // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
- PublicKey publicKey;
+
+ // TODO: Check the public-key signature on the whole file before parsing it
+
+ CertXml certXml;
+ try {
+ certXml = CertXml.parse(recoveryServiceCertFile);
+ } catch (CertParsingException e) {
+ // TODO: Do not use raw key bytes anymore once the other components are updated
+ Log.d(TAG, "Failed to parse the cert file", e);
+ PublicKey publicKey = parseEcPublicKey(recoveryServiceCertFile);
+ if (mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey) > 0) {
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
+ return;
+ }
+
+ // Check serial number
+ long newSerial = certXml.getSerial();
+ Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid);
+ if (oldSerial != null && oldSerial >= newSerial) {
+ if (oldSerial == newSerial) {
+ Log.i(TAG, "The cert file serial number is the same, so skip updating.");
+ } else {
+ Log.e(TAG, "The cert file serial number is older than the one in database.");
+ }
+ return;
+ }
+ Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
+
+ CertPath certPath;
+ try {
+ Log.d(TAG, "Getting and validating a random endpoint certificate");
+ certPath = certXml.getRandomEndpointCert(TrustedRootCert.TRUSTED_ROOT_CERT);
+ } catch (CertValidationException e) {
+ Log.e(TAG, "Invalid endpoint cert", e);
+ throw new ServiceSpecificException(
+ ERROR_BAD_CERTIFICATE_FORMAT, "Failed to validate certificate.");
+ }
+ try {
+ Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
+ if (mDatabase.setRecoveryServiceCertPath(userId, uid, certPath) > 0) {
+ mDatabase.setRecoveryServiceCertSerial(userId, uid, newSerial);
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
+ } catch (CertificateEncodingException e) {
+ Log.e(TAG, "Failed to encode CertPath", e);
+ throw new ServiceSpecificException(
+ ERROR_BAD_CERTIFICATE_FORMAT, "Failed to encode CertPath.");
+ }
+ }
+
+ private PublicKey parseEcPublicKey(@NonNull byte[] bytes) throws ServiceSpecificException {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
- // TODO: Randomly choose a key from the list -- right now we just use the whole input
- X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
- publicKey = kf.generatePublic(pkSpec);
+ X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(bytes);
+ return kf.generatePublic(pkSpec);
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
@@ -171,10 +225,6 @@
throw new ServiceSpecificException(
ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
}
- long updatedRows = mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey);
- if (updatedRows > 0) {
- mDatabase.setShouldCreateSnapshot(userId, uid, true);
- }
}
/**
@@ -519,11 +569,11 @@
byte[] locallyEncryptedKey;
try {
// TODO: Remove the extraneous logging here
- Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
+ Log.d(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
sessionEntry.getKeyClaimant()));
- Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
+ Log.d(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
sessionEntry.getVaultParams()));
- Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
+ Log.d(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
sessionEntry.getKeyClaimant(),
sessionEntry.getVaultParams(),
@@ -543,9 +593,9 @@
try {
// TODO: Remove the extraneous logging here
- Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
+ Log.d(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
sessionEntry.getLskfHash()));
- Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
+ Log.d(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
} catch (InvalidKeyException e) {
Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
@@ -585,8 +635,8 @@
try {
// TODO: Remove the extraneous logging here
- Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey));
- Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial));
+ Log.d(TAG, constructLoggingMessage("recoveryKey", recoveryKey));
+ Log.d(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial));
byte[] keyMaterial =
KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
keyMaterialByAlias.put(alias, keyMaterial);
@@ -600,13 +650,16 @@
throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
"Failed to recover key with alias '" + alias + "': " + e.getMessage());
} catch (AEADBadTagException e) {
- // TODO: Remove the extraneous logging here
Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
+ alias, e);
- throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
- "Failed to recover key with alias '" + alias + "': " + e.getMessage());
+ // Ignore the exception to continue to recover the other application keys.
}
}
+ if (keyMaterialByAlias.isEmpty()) {
+ Log.e(TAG, "Failed to recover any of the application keys.");
+ throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+ "Failed to recover any of the application keys.");
+ }
return keyMaterialByAlias;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
index 985f5b6..09ec5ad 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
@@ -18,6 +18,7 @@
import static javax.xml.xpath.XPathConstants.NODESET;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -25,6 +26,8 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -65,20 +68,21 @@
import org.xml.sax.SAXException;
/** Utility functions related to parsing and validating public-key certificates. */
-final class CertUtils {
+public final class CertUtils {
private static final String CERT_FORMAT = "X.509";
private static final String CERT_PATH_ALG = "PKIX";
private static final String CERT_STORE_ALG = "Collection";
private static final String SIGNATURE_ALG = "SHA256withRSA";
- private CertUtils() {}
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({MUST_EXIST_UNENFORCED, MUST_EXIST_EXACTLY_ONE, MUST_EXIST_AT_LEAST_ONE})
+ @interface MustExist {}
+ static final int MUST_EXIST_UNENFORCED = 0;
+ static final int MUST_EXIST_EXACTLY_ONE = 1;
+ static final int MUST_EXIST_AT_LEAST_ONE = 2;
- enum MustExist {
- FALSE,
- EXACTLY_ONE,
- AT_LEAST_ONE,
- }
+ private CertUtils() {}
/**
* Decodes a byte array containing an encoded X509 certificate.
@@ -159,7 +163,7 @@
* @return a list of strings that are the text contents of the child nodes
* @throws CertParsingException if any parsing error occurs
*/
- static List<String> getXmlNodeContents(MustExist mustExist, Element rootNode,
+ static List<String> getXmlNodeContents(@MustExist int mustExist, Element rootNode,
String... nodeTags)
throws CertParsingException {
String expression = String.join("/", nodeTags);
@@ -173,10 +177,10 @@
}
switch (mustExist) {
- case FALSE:
+ case MUST_EXIST_UNENFORCED:
break;
- case EXACTLY_ONE:
+ case MUST_EXIST_EXACTLY_ONE:
if (nodeList.getLength() != 1) {
throw new CertParsingException(
"The XML file must contain exactly one node with the path "
@@ -184,7 +188,7 @@
}
break;
- case AT_LEAST_ONE:
+ case MUST_EXIST_AT_LEAST_ONE:
if (nodeList.getLength() == 0) {
throw new CertParsingException(
"The XML file must contain at least one node with the path "
@@ -194,7 +198,7 @@
default:
throw new UnsupportedOperationException(
- "This enum value of MustExist is not supported: " + mustExist);
+ "This value of MustExist is not supported: " + mustExist);
}
List<String> result = new ArrayList<>();
@@ -213,7 +217,7 @@
* @return the decoding decoding result
* @throws CertParsingException if the input string is not a properly base64-encoded string
*/
- static byte[] decodeBase64(String str) throws CertParsingException {
+ public static byte[] decodeBase64(String str) throws CertParsingException {
try {
return Base64.getDecoder().decode(str);
} catch (IllegalArgumentException e) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
index 2c04a86..c62a31e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java
@@ -129,7 +129,7 @@
private static long parseSerial(Element rootNode) throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.EXACTLY_ONE,
+ CertUtils.MUST_EXIST_EXACTLY_ONE,
rootNode,
METADATA_NODE_TAG,
METADATA_SERIAL_NODE_TAG);
@@ -139,7 +139,7 @@
private static long parseRefreshInterval(Element rootNode) throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.EXACTLY_ONE,
+ CertUtils.MUST_EXIST_EXACTLY_ONE,
rootNode,
METADATA_NODE_TAG,
METADATA_REFRESH_INTERVAL_NODE_TAG);
@@ -150,7 +150,7 @@
throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.FALSE,
+ CertUtils.MUST_EXIST_UNENFORCED,
rootNode,
INTERMEDIATE_CERT_LIST_TAG,
INTERMEDIATE_CERT_ITEM_TAG);
@@ -165,7 +165,7 @@
throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.AT_LEAST_ONE,
+ CertUtils.MUST_EXIST_AT_LEAST_ONE,
rootNode,
ENDPOINT_CERT_LIST_TAG,
ENDPOINT_CERT_ITEM_TAG);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
index 878fc6e..e75be85 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java
@@ -94,7 +94,7 @@
throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.FALSE,
+ CertUtils.MUST_EXIST_UNENFORCED,
rootNode,
INTERMEDIATE_CERT_LIST_TAG,
INTERMEDIATE_CERT_ITEM_TAG);
@@ -108,14 +108,14 @@
private static X509Certificate parseSignerCert(Element rootNode) throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG);
+ CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG);
return CertUtils.decodeCert(CertUtils.decodeBase64(contents.get(0)));
}
private static byte[] parseFileSignature(Element rootNode) throws CertParsingException {
List<String> contents =
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG);
+ CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG);
return CertUtils.decodeBase64(contents.get(0));
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java
new file mode 100644
index 0000000..7195d62
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.certificate;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * Holds the X509 certificate of the trusted root CA cert for the recoverable key store service.
+ *
+ * TODO: Read the certificate from a PEM file directly and remove this class.
+ */
+public final class TrustedRootCert {
+
+ private static final String TRUSTED_ROOT_CERT_BASE64 = ""
+ + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV"
+ + "BAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDIxOTM5MTRaFw0zODAx"
+ + "MjgxOTM5MTRaMCAxHjAcBgNVBAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDCCAiIw"
+ + "DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2OT5i40/H7LINg/lq/0G0hR65P"
+ + "Q4Mud3OnuVt6UIYV2T18+v6qW1yJd5FcnND/ZKPau4aUAYklqJuSVjOXQD0BjgS2"
+ + "98Xa4dSn8Ci1rUR+5tdmrxqbYUdT2ZvJIUMMR6fRoqi+LlAbKECrV+zYQTyLU68w"
+ + "V66hQpAButjJKiZzkXjmKLfJ5IWrNEn17XM988rk6qAQn/BYCCQGf3rQuJeksGmA"
+ + "N1lJOwNYxmWUyouVwqwZthNEWqTuEyBFMkAT+99PXW7oVDc7oU5cevuihxQWNTYq"
+ + "viGB8cck6RW3cmqrDSaJF/E+N0cXFKyYC7FDcggt6k3UrxNKTuySdDEa8+2RTQqU"
+ + "Y9npxBlQE+x9Ig56OI1BG3bSBsGdPgjpyHadZeh2tgk+oqlGsSsum24YxaxuSysT"
+ + "Qfcu/XhyfUXavfmGrBOXerTzIl5oBh/F5aHTV85M2tYEG0qsPPvSpZAWtdJ/2rca"
+ + "OxvhwOL+leZKr8McjXVR00lBsRuKXX4nTUMwya09CO3QHFPFZtZvqjy2HaMOnVLQ"
+ + "I6b6dHEfmsHybzVOe3yPEoFQSU9UhUdmi71kwwoanPD3j9fJHmXTx4PzYYBRf1ZE"
+ + "o+uPgMPk7CDKQFZLjnR40z1uzu3O8aZ3AKZzP+j7T4XQKJLQLmllKtPgLgNdJyib"
+ + "2Glg7QhXH/jBTL6hAgMBAAGjYzBhMB0GA1UdDgQWBBSbZfrqOYH54EJpkdKMZjMc"
+ + "z/Hp+DAfBgNVHSMEGDAWgBSbZfrqOYH54EJpkdKMZjMcz/Hp+DAPBgNVHRMBAf8E"
+ + "BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQ0FAAOCAgEAKh9nm/vW"
+ + "glMWp3vcCwWwJW286ecREDlI+CjGh5h+f2N4QRrXd/tKE3qQJWCqGx8sFfIUjmI7"
+ + "KYdsC2gyQ2cA2zl0w7pB2QkuqE6zVbnh1D17Hwl19IMyAakFaM9ad4/EoH7oQmqX"
+ + "nF/f5QXGZw4kf1HcgKgoCHWXjqR8MqHOcXR8n6WFqxjzJf1jxzi6Yo2dZ7PJbnE6"
+ + "+kHIJuiCpiHL75v5g1HM41gT3ddFFSrn88ThNPWItT5Z8WpFjryVzank2Yt02LLl"
+ + "WqZg9IC375QULc5B58NMnaiVJIDJQ8zoNgj1yaxqtUMnJX570lotO2OXe4ec9aCQ"
+ + "DIJ84YLM/qStFdeZ9416E80dchskbDG04GuVJKlzWjxAQNMRFhyaPUSBTLLg+kwP"
+ + "t9+AMmc+A7xjtFQLZ9fBYHOBsndJOmeSQeYeckl+z/1WQf7DdwXn/yijon7mxz4z"
+ + "cCczfKwTJTwBh3wR5SQr2vQm7qaXM87qxF8PCAZrdZaw5I80QwkgTj0WTZ2/GdSw"
+ + "d3o5SyzzBAjpwtG+4bO/BD9h9wlTsHpT6yWOZs4OYAKU5ykQrncI8OyavMggArh3"
+ + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8"
+ + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA=";
+
+ /**
+ * The X509 certificate of the trusted root CA cert for the recoverable key store service.
+ *
+ * TODO: Change it to the production certificate root CA before the final launch.
+ */
+ public static final X509Certificate TRUSTED_ROOT_CERT;
+
+ static {
+ try {
+ TRUSTED_ROOT_CERT = CertUtils.decodeCert(
+ CertUtils.decodeBase64(TRUSTED_ROOT_CERT_BASE64));
+ } catch (CertParsingException e) {
+ // Should not happen
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index b96208d..89ddb6c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -31,9 +31,14 @@
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
@@ -53,6 +58,7 @@
private static final String TAG = "RecoverableKeyStoreDb";
private static final int IDLE_TIMEOUT_SECONDS = 30;
private static final int LAST_SYNCED_AT_UNSYNCED = -1;
+ private static final String CERT_PATH_ENCODING = "PkiPath";
private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
@@ -361,6 +367,79 @@
}
/**
+ * Returns the serial number of the XML file containing certificates of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @return The value that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public Long getRecoveryServiceCertSerial(int userId, int uid) {
+ return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL);
+ }
+
+ /**
+ * Records the serial number of the XML file containing certificates of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @param serial The serial number contained in the XML file for recovery service certificates.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long setRecoveryServiceCertSerial(int userId, int uid, long serial) {
+ return setLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, serial);
+ }
+
+ /**
+ * Returns the {@code CertPath} of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @return The value that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public CertPath getRecoveryServiceCertPath(int userId, int uid) {
+ byte[] bytes = getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH);
+ if (bytes == null) {
+ return null;
+ }
+ try {
+ return decodeCertPath(bytes);
+ } catch (CertificateException e) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "Recovery service CertPath entry cannot be decoded for "
+ + "userId=%d uid=%d.",
+ userId, uid), e);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the {@code CertPath} of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @param certPath The certificate path of the recovery service.
+ * @return The primary key of the inserted row, or -1 if failed.
+ * @hide
+ */
+ public long setRecoveryServiceCertPath(int userId, int uid, CertPath certPath) throws
+ CertificateEncodingException {
+ if (certPath.getCertificates().size() == 0) {
+ throw new CertificateEncodingException("No certificate contained in the cert path.");
+ }
+ return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH,
+ certPath.getEncoded(CERT_PATH_ENCODING));
+ }
+
+ /**
* Returns the list of recovery agents initialized for given {@code userId}
* @param userId The userId of the profile the application is running under.
* @return The list of recovery agents
@@ -515,48 +594,6 @@
}
/**
- * Returns the first (and only?) public key for {@code userId}.
- *
- * @param userId The userId of the profile whose keys are to be synced.
- * @return The public key, or null if none exists.
- */
- @Nullable
- public PublicKey getRecoveryServicePublicKey(int userId) {
- SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
-
- String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY };
- String selection =
- RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
- String[] selectionArguments = { Integer.toString(userId) };
-
- try (
- Cursor cursor = db.query(
- RecoveryServiceMetadataEntry.TABLE_NAME,
- projection,
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)
- ) {
- if (cursor.getCount() < 1) {
- return null;
- }
-
- cursor.moveToFirst();
- byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
- RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY));
-
- try {
- return decodeX509Key(keyBytes);
- } catch (InvalidKeySpecException e) {
- Log.wtf(TAG, "Could not decode public key for " + userId);
- return null;
- }
- }
- }
-
- /**
* Updates the counterId
*
* @param userId The userId of the profile the application is running under.
@@ -585,7 +622,6 @@
return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID);
}
-
/**
* Updates the server parameters given by the application initializing the local recovery
* components.
@@ -869,4 +905,16 @@
throw new RuntimeException(e);
}
}
+
+ @Nullable
+ private static CertPath decodeCertPath(byte[] bytes) throws CertificateException {
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ // Should not happen, as X.509 is mandatory for all providers.
+ throw new RuntimeException(e);
+ }
+ return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 4ee282b..1cb5d91 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -104,9 +104,10 @@
static final String COLUMN_NAME_UID = "uid";
/**
- * Version of the latest recovery snapshot
+ * Version of the latest recovery snapshot.
*/
static final String COLUMN_NAME_SNAPSHOT_VERSION = "snapshot_version";
+
/**
* Flag to generate new snapshot.
*/
@@ -118,6 +119,16 @@
static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
/**
+ * The certificate path of the recovery service.
+ */
+ static final String COLUMN_NAME_CERT_PATH = "cert_path";
+
+ /**
+ * The serial number contained in the certificate XML file of the recovery service.
+ */
+ static final String COLUMN_NAME_CERT_SERIAL = "cert_serial";
+
+ /**
* Secret types used for end-to-end encryption.
*/
static final String COLUMN_NAME_SECRET_TYPES = "secret_types";
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 79fd496..8a89f2d 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
@@ -28,7 +29,9 @@
* Helper for creating the recoverable key database.
*/
class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
- private static final int DATABASE_VERSION = 2;
+ private static final String TAG = "RecoverableKeyStoreDbHp";
+
+ static final int DATABASE_VERSION = 3;
private static final String DATABASE_NAME = "recoverablekeystore.db";
private static final String SQL_CREATE_KEYS_ENTRY =
@@ -59,6 +62,8 @@
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
@@ -88,9 +93,39 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL(SQL_DELETE_KEYS_ENTRY);
- db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
- db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
- onCreate(db);
+ if (oldVersion < 2) {
+ db.execSQL(SQL_DELETE_KEYS_ENTRY);
+ db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
+ db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
+ onCreate(db);
+ return;
+ }
+
+ if (oldVersion < 3) {
+ upgradeDbForVersion3(db);
+ }
+ }
+
+ private void upgradeDbForVersion3(SQLiteDatabase db) {
+ // Add the two columns for cert path and cert serial number
+ addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, "BLOB", /*defaultStr=*/ null);
+ addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, "INTEGER", /*defaultStr=*/
+ null);
+ }
+
+ private static void addColumnToTable(
+ SQLiteDatabase db, String tableName, String column, String columnType,
+ String defaultStr) {
+ Log.d(TAG, "Adding column " + column + " to " + tableName + ".");
+
+ String alterStr = "ALTER TABLE " + tableName + " ADD COLUMN " + column + " " + columnType;
+ if (defaultStr != null && !defaultStr.isEmpty()) {
+ alterStr += " DEFAULT " + defaultStr;
+ }
+
+ db.execSQL(alterStr + ";");
}
}
+
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index 7881a95..648c782 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -289,6 +289,7 @@
}
}
+ @GuardedBy("mLock")
private void sendAudioPlayerActiveStateChangedMessageLocked(
final AudioPlaybackConfiguration config, final boolean isRemoved) {
for (MessageHandler messageHandler : mListenerMap.values()) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index a6f049e..f346629 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1722,7 +1722,7 @@
final long totalBytes = getTotalBytes(
NetworkTemplate.buildTemplateMobileAll(state.subscriberId), start, end);
final long remainingBytes = limitBytes - totalBytes;
- final long remainingDays = Math.min(1, (end - currentTimeMillis())
+ final long remainingDays = Math.max(1, (end - currentTimeMillis())
/ TimeUnit.DAYS.toMillis(1));
if (remainingBytes > 0) {
quotaBytes = (remainingBytes / remainingDays) / 10;
@@ -4678,10 +4678,12 @@
return subId;
}
+ @GuardedBy("mNetworkPoliciesSecondLock")
private int getSubIdLocked(Network network) {
return mNetIdToSubId.get(network.netId, INVALID_SUBSCRIPTION_ID);
}
+ @GuardedBy("mNetworkPoliciesSecondLock")
private SubscriptionPlan getPrimarySubscriptionPlanLocked(int subId) {
final SubscriptionPlan[] plans = mSubscriptionPlans.get(subId);
return ArrayUtils.isEmpty(plans) ? null : plans[0];
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 961a451..3cc4d83 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -187,6 +187,7 @@
*/
@VisibleForTesting
public static long multiplySafe(long value, long num, long den) {
+ if (den == 0) den = 1;
long x = value;
long y = num;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index bfc150e..76c4db1 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -405,6 +405,7 @@
mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
}
+ @GuardedBy("mStatsLock")
private void shutdownLocked() {
mContext.unregisterReceiver(mTetherReceiver);
mContext.unregisterReceiver(mPollReceiver);
@@ -431,6 +432,7 @@
mSystemReady = false;
}
+ @GuardedBy("mStatsLock")
private void maybeUpgradeLegacyStatsLocked() {
File file;
try {
@@ -909,6 +911,7 @@
* reflect current {@link #mPersistThreshold} value. Always defers to
* {@link Global} values when defined.
*/
+ @GuardedBy("mStatsLock")
private void updatePersistThresholdsLocked() {
mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
@@ -1029,6 +1032,7 @@
* are active on a single {@code iface}, they are combined under a single
* {@link NetworkIdentitySet}.
*/
+ @GuardedBy("mStatsLock")
private void updateIfacesLocked(Network[] defaultNetworks) {
if (!mSystemReady) return;
if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
@@ -1128,6 +1132,7 @@
return ident;
}
+ @GuardedBy("mStatsLock")
private void recordSnapshotLocked(long currentTime) throws RemoteException {
// snapshot and record current counters; read UID stats first to
// avoid over counting dev stats.
@@ -1163,6 +1168,7 @@
* Bootstrap initial stats snapshot, usually during {@link #systemReady()}
* so we have baseline values without double-counting.
*/
+ @GuardedBy("mStatsLock")
private void bootstrapStatsLocked() {
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
: System.currentTimeMillis();
@@ -1197,6 +1203,7 @@
* Periodic poll operation, reading current statistics and recording into
* {@link NetworkStatsHistory}.
*/
+ @GuardedBy("mStatsLock")
private void performPollLocked(int flags) {
if (!mSystemReady) return;
if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
@@ -1258,6 +1265,7 @@
/**
* Sample recent statistics summary into {@link EventLog}.
*/
+ @GuardedBy("mStatsLock")
private void performSampleLocked() {
// TODO: migrate trustedtime fixes to separate binary log events
final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1;
@@ -1295,6 +1303,7 @@
/**
* Clean up {@link #mUidRecorder} after UID is removed.
*/
+ @GuardedBy("mStatsLock")
private void removeUidsLocked(int... uids) {
if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
@@ -1313,6 +1322,7 @@
/**
* Clean up {@link #mUidRecorder} after user is removed.
*/
+ @GuardedBy("mStatsLock")
private void removeUserLocked(int userId) {
if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId);
@@ -1434,6 +1444,7 @@
}
}
+ @GuardedBy("mStatsLock")
private void dumpProtoLocked(FileDescriptor fd) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
index c4de4ac..e8b39c0 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -22,12 +22,14 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
@@ -128,7 +130,7 @@
Slog.e(TAG, "Couldn't find package: " + packageNames);
return false;
}
- ai = mPm.getApplicationInfo(packageNames[0],0);
+ ai = mPm.getApplicationInfo(packageNames[0], 0);
} catch (NameNotFoundException e) {
// Should not happen.
return false;
@@ -136,7 +138,7 @@
return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
}
- /**
+ /**
* Report network watchlist records if we collected enough data.
*/
public void reportWatchlistIfNecessary() {
@@ -180,6 +182,10 @@
return true;
}
final byte[] digest = getDigestFromUid(uid);
+ if (digest == null) {
+ Slog.e(TAG, "Cannot get digest from uid: " + uid);
+ return false;
+ }
final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
tryAggregateRecords();
return result;
@@ -234,15 +240,34 @@
* if an app is really visited C&C site.
* (2) App digests that previously recorded in database.
*/
- private List<String> getAllDigestsForReport(WatchlistReportDbHelper.AggregatedResult record) {
+ @VisibleForTesting
+ List<String> getAllDigestsForReport(WatchlistReportDbHelper.AggregatedResult record) {
// Step 1: Get all installed application digests.
+ final List<UserInfo> users = ((UserManager) mContext.getSystemService(
+ Context.USER_SERVICE)).getUsers();
+ final int totalUsers = users.size();
final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
final HashSet<String> result = new HashSet<>(apps.size() + record.appDigestCNCList.size());
final int size = apps.size();
for (int i = 0; i < size; i++) {
- byte[] digest = getDigestFromUid(apps.get(i).uid);
- result.add(HexDump.toHexString(digest));
+ final int appUid = apps.get(i).uid;
+ boolean added = false;
+ // As the uid returned by getInstalledApplications() is for primary user only, it
+ // may exist in secondary users but not primary user, so we need to loop and see if
+ // that user has the app enabled.
+ for (int j = 0; j < totalUsers && !added; j++) {
+ int uid = UserHandle.getUid(users.get(j).id, appUid);
+ byte[] digest = getDigestFromUid(uid);
+ if (digest != null) {
+ result.add(HexDump.toHexString(digest));
+ added = true;
+ }
+ }
+ if (!added) {
+ Slog.e(TAG, "Cannot get digest from uid: " + apps.get(i).uid
+ + ",pkg: " + apps.get(i).packageName);
+ }
}
// Step 2: Add all digests from records
result.addAll(record.appDigestCNCList.keySet());
@@ -279,9 +304,9 @@
return null;
}
}
- } else {
- Slog.e(TAG, "Should not happen");
}
+ // Not able to find a package name for this uid, possibly the package is installed on
+ // another user.
return null;
});
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index fd435f9..b7842d5 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -376,9 +376,7 @@
protected void upgradeXml(final int xmlVersion, final int userId) {}
private void loadAllowedComponentsFromSettings() {
-
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- for (UserInfo user : userManager.getUsers()) {
+ for (UserInfo user : mUm.getUsers()) {
final ContentResolver cr = mContext.getContentResolver();
addApprovedList(Settings.Secure.getStringForUser(
cr,
@@ -482,7 +480,9 @@
for (int i = 0; i < allowedByType.size(); i++) {
final ArraySet<String> allowed = allowedByType.valueAt(i);
allowedPackages.addAll(
- allowed.stream().map(this::getPackageName).collect(Collectors.toList()));
+ allowed.stream().map(this::getPackageName).
+ filter(value -> !TextUtils.isEmpty(value))
+ .collect(Collectors.toList()));
}
return allowedPackages;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e7eed03..3800017 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,7 +25,9 @@
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService
.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -88,6 +90,7 @@
import android.companion.ICompanionDeviceManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -115,6 +118,7 @@
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -206,6 +210,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
@@ -288,6 +293,7 @@
private ICompanionDeviceManager mCompanionManager;
private AccessibilityManager mAccessibilityManager;
private IDeviceIdleController mDeviceIdleController;
+ private IBinder mPermissionOwner;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -524,7 +530,7 @@
} catch (FileNotFoundException e) {
// No data yet
// Load default managed services approvals
- readDefaultApprovedServices(UserHandle.USER_SYSTEM);
+ readDefaultApprovedServices(USER_SYSTEM);
} catch (IOException e) {
Log.wtf(TAG, "Unable to read notification policy", e);
} catch (NumberFormatException e) {
@@ -680,6 +686,7 @@
sbn.getId(), Notification.FLAG_AUTO_CANCEL,
Notification.FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
REASON_CLICK, null);
+ reportUserInteraction(r);
}
}
@@ -700,7 +707,7 @@
.setSubtype(actionIndex));
EventLogTags.writeNotificationActionClicked(key, actionIndex,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
- // TODO: Log action click via UsageStats.
+ reportUserInteraction(r);
}
}
@@ -821,6 +828,7 @@
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
r.recordDirectReplied();
+ reportUserInteraction(r);
}
}
}
@@ -974,7 +982,7 @@
final int enabled = mPackageManager.getApplicationEnabledSetting(
pkgName,
changeUserId != UserHandle.USER_ALL ? changeUserId :
- UserHandle.USER_SYSTEM);
+ USER_SYSTEM);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
cancelNotifications = false;
@@ -1061,6 +1069,7 @@
}
} else if (action.equals(Intent.ACTION_USER_REMOVED)) {
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mUserProfiles.updateCache(context);
mZenModeHelper.onUserRemoved(user);
mRankingHelper.onUserRemoved(user);
mListeners.onUserRemoved(user);
@@ -1268,7 +1277,7 @@
NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
NotificationUsageStats usageStats, AtomicFile policyFile,
- ActivityManager activityManager, GroupHelper groupHelper) {
+ ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1276,7 +1285,7 @@
mAccessibilityManager =
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
- mAm = ActivityManager.getService();
+ mAm = am;
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
@@ -1287,6 +1296,11 @@
mActivityManager = activityManager;
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ try {
+ mPermissionOwner = mAm.newUriPermissionOwner("notification");
+ } catch (RemoteException e) {
+ Slog.w(TAG, "AM dead", e);
+ }
mHandler = new WorkerHandler(looper);
mRankingThread.start();
@@ -1415,7 +1429,7 @@
null, snoozeHelper, new NotificationUsageStats(getContext()),
new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
(ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
- getGroupHelper());
+ getGroupHelper(), ActivityManager.getService());
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -1746,14 +1760,29 @@
return INotificationManager.Stub.asInterface(mService);
}
+ /**
+ * Report to usage stats that the notification was seen.
+ * @param r notification record
+ */
protected void reportSeen(NotificationRecord r) {
final int userId = r.sbn.getUserId();
mAppUsageStats.reportEvent(r.sbn.getPackageName(),
- userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
+ userId == UserHandle.USER_ALL ? USER_SYSTEM
: userId,
UsageEvents.Event.NOTIFICATION_SEEN);
}
+ /**
+ * Report to usage stats that the notification was clicked.
+ * @param r notification record
+ */
+ protected void reportUserInteraction(NotificationRecord r) {
+ final int userId = r.sbn.getUserId();
+ mAppUsageStats.reportEvent(r.sbn.getPackageName(),
+ userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId,
+ UsageEvents.Event.USER_INTERACTION);
+ }
+
@VisibleForTesting
NotificationManagerInternal getInternalService() {
return mInternalService;
@@ -2894,7 +2923,7 @@
checkCallerIsSystem();
if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
//TODO: http://b/22388012
- if (user != UserHandle.USER_SYSTEM) {
+ if (user != USER_SYSTEM) {
Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
return null;
}
@@ -2920,7 +2949,7 @@
return;
}
//TODO: http://b/22388012
- if (user != UserHandle.USER_SYSTEM) {
+ if (user != USER_SYSTEM) {
Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
return;
}
@@ -3678,7 +3707,7 @@
sbn.getNotification().flags =
(r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
mRankingHelper.sort(mNotificationList);
- mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
+ mListeners.notifyPostedLocked(r, sbn /* oldSbn */);
}
};
@@ -3707,7 +3736,7 @@
try {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
+ (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
int canColorize = mPackageManagerClient.checkPermission(
@@ -3897,6 +3926,7 @@
return true;
}
+ @GuardedBy("mNotificationLock")
protected int getNotificationCountLocked(String pkg, int userId, int excludedId,
String excludedTag) {
int count = 0;
@@ -4126,6 +4156,8 @@
// Make sure we don't lose the foreground service state.
notification.flags |=
old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+ // revoke uri permissions for changed uris
+ revokeUriPermissions(r, old);
r.isUpdate = true;
}
@@ -4147,7 +4179,7 @@
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
- mListeners.notifyPostedLocked(n, oldSbn);
+ mListeners.notifyPostedLocked(r, oldSbn);
if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
mHandler.post(new Runnable() {
@Override
@@ -4912,6 +4944,9 @@
r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
}
+ // Revoke permissions
+ revokeUriPermissions(null, r);
+
// tell the app
if (sendDelete) {
if (r.getNotification().deleteIntent != null) {
@@ -5009,6 +5044,30 @@
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), listenerName);
}
+ void revokeUriPermissions(NotificationRecord newRecord, NotificationRecord oldRecord) {
+ Set<Uri> oldUris = oldRecord.getNotificationUris();
+ Set<Uri> newUris = newRecord == null ? new HashSet<>() : newRecord.getNotificationUris();
+ oldUris.removeAll(newUris);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ for (Uri uri : oldUris) {
+ if (uri != null) {
+ int notiUserId = oldRecord.getUserId();
+ int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM
+ : ContentProvider.getUserIdFromUri(uri, notiUserId);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner,
+ uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Count not revoke uri permissions", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
* and none of the {@code mustNotHaveFlags}.
@@ -5851,10 +5910,13 @@
* but isn't anymore.
*/
@GuardedBy("mNotificationLock")
- public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
+ public void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn) {
// Lazily initialized snapshots of the notification.
+ StatusBarNotification sbn = r.sbn;
TrimCache trimCache = new TrimCache(sbn);
+ Set<Uri> uris = r.getNotificationUris();
+
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
@@ -5877,6 +5939,9 @@
continue;
}
+ grantUriPermissions(uris, sbn.getUserId(), info.component.getPackageName(),
+ info.userid);
+
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(new Runnable() {
@Override
@@ -5887,6 +5952,28 @@
}
}
+ private void grantUriPermissions(Set<Uri> uris, int notiUserId, String listenerPkg,
+ int listenerUserId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ for (Uri uri : uris) {
+ if (uri != null) {
+ int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM
+ : ContentProvider.getUserIdFromUri(uri, notiUserId);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(),
+ listenerPkg,
+ uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId,
+ listenerUserId == USER_ALL ? USER_SYSTEM : listenerUserId);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Count not grant uri permission to " + listenerPkg, e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* asynchronously notify all listeners about a removed notification
*/
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 23b9743..1ad8c74 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -38,6 +38,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Adjustment;
@@ -47,6 +48,7 @@
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
@@ -64,6 +66,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Holds data about notifications that should not be shared with the
@@ -929,6 +932,42 @@
mStats.setViewedSettings();
}
+ public Set<Uri> getNotificationUris() {
+ Notification notification = getNotification();
+ Set<Uri> uris = new ArraySet<>();
+
+ if (notification.sound != null) {
+ uris.add(notification.sound);
+ }
+ if (notification.getChannelId() != null) {
+ NotificationChannel channel = getChannel();
+ if (channel != null && channel.getSound() != null) {
+ uris.add(channel.getSound());
+ }
+ }
+ if (notification.extras.containsKey(Notification.EXTRA_AUDIO_CONTENTS_URI)) {
+ uris.add(notification.extras.getParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI));
+ }
+ if (notification.extras.containsKey(Notification.EXTRA_BACKGROUND_IMAGE_URI)) {
+ uris.add(notification.extras.getParcelable(Notification.EXTRA_BACKGROUND_IMAGE_URI));
+ }
+ if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
+ Parcelable[] newMessages =
+ notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ List<Notification.MessagingStyle.Message> messages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(newMessages);
+ Parcelable[] histMessages =
+ notification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+ messages.addAll(
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages));
+ for (Notification.MessagingStyle.Message message : messages) {
+ uris.add(message.getDataUri());
+ }
+ }
+
+ return uris;
+ }
+
public LogMaker getLogMaker(long now) {
if (mLogMaker == null) {
// initialize fields that only change on update (so a new record)
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 7600e81..6e02db7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -20,6 +20,8 @@
import static android.content.om.OverlayInfo.STATE_ENABLED;
import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
+import static android.content.om.OverlayInfo.STATE_OVERLAY_UPGRADING;
+import static android.content.om.OverlayInfo.STATE_TARGET_UPGRADING;
import static com.android.server.om.OverlayManagerService.DEBUG;
import static com.android.server.om.OverlayManagerService.TAG;
@@ -50,6 +52,10 @@
* @see OverlayManagerService
*/
final class OverlayManagerServiceImpl {
+ // Flags to use in conjunction with updateState.
+ private static final int FLAG_TARGET_IS_UPGRADING = 1<<0;
+ private static final int FLAG_OVERLAY_IS_UPGRADING = 1<<1;
+
private final PackageManagerHelper mPackageManager;
private final IdmapManager mIdmapManager;
private final OverlayManagerSettings mSettings;
@@ -123,9 +129,7 @@
}
try {
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(overlayPackage.overlayTarget, newUserId);
- updateState(targetPackage, overlayPackage, newUserId);
+ updateState(overlayPackage.overlayTarget, overlayPackage.packageName, newUserId, 0);
} catch (OverlayManagerSettings.BadKeyException e) {
Slog.e(TAG, "failed to update settings", e);
mSettings.remove(overlayPackage.packageName, newUserId);
@@ -168,8 +172,7 @@
Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
}
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -179,18 +182,18 @@
Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
}
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) {
if (DEBUG) {
- Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId);
+ Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId="
+ + userId);
}
- if (updateAllOverlaysForTarget(packageName, userId, null)) {
+ if (updateAllOverlaysForTarget(packageName, userId, FLAG_TARGET_IS_UPGRADING)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -200,8 +203,7 @@
Slog.d(TAG, "onTargetPackageUpgraded packageName=" + packageName + " userId=" + userId);
}
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -211,7 +213,7 @@
Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
}
- if (updateAllOverlaysForTarget(packageName, userId, null)) {
+ if (updateAllOverlaysForTarget(packageName, userId, 0)) {
mListener.onOverlaysChanged(packageName, userId);
}
}
@@ -219,20 +221,21 @@
/**
* Returns true if the settings were modified for this target.
*/
- private boolean updateAllOverlaysForTarget(@NonNull final String packageName, final int userId,
- @Nullable final PackageInfo targetPackage) {
+ private boolean updateAllOverlaysForTarget(@NonNull final String targetPackageName,
+ final int userId, final int flags) {
boolean modified = false;
- final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(packageName, userId);
+ final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(targetPackageName, userId);
final int N = ois.size();
for (int i = 0; i < N; i++) {
final OverlayInfo oi = ois.get(i);
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, userId);
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName,
+ userId);
if (overlayPackage == null) {
modified |= mSettings.remove(oi.packageName, oi.userId);
removeIdmapIfPossible(oi);
} else {
try {
- modified |= updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(targetPackageName, oi.packageName, userId, flags);
} catch (OverlayManagerSettings.BadKeyException e) {
Slog.e(TAG, "failed to update settings", e);
modified |= mSettings.remove(oi.packageName, userId);
@@ -254,14 +257,11 @@
return;
}
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(overlayPackage.overlayTarget, userId);
-
mSettings.init(packageName, userId, overlayPackage.overlayTarget,
overlayPackage.applicationInfo.getBaseCodePath(),
overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority);
try {
- if (updateState(targetPackage, overlayPackage, userId)) {
+ if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
}
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -271,15 +271,64 @@
}
void onOverlayPackageChanged(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageChanged called, but only pre-installed overlays supported");
+ if (DEBUG) {
+ Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
+ }
+
+ try {
+ final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+ if (updateState(oi.targetPackageName, packageName, userId, 0)) {
+ mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ }
}
void onOverlayPackageUpgrading(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageUpgrading called, but only pre-installed overlays supported");
+ if (DEBUG) {
+ Slog.d(TAG, "onOverlayPackageUpgrading packageName=" + packageName + " userId="
+ + userId);
+ }
+
+ try {
+ final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+ if (updateState(oi.targetPackageName, packageName, userId, FLAG_OVERLAY_IS_UPGRADING)) {
+ removeIdmapIfPossible(oi);
+ mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ }
}
void onOverlayPackageUpgraded(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageUpgraded called, but only pre-installed overlays supported");
+ if (DEBUG) {
+ Slog.d(TAG, "onOverlayPackageUpgraded packageName=" + packageName + " userId="
+ + userId);
+ }
+
+ final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
+ if (pkg == null) {
+ Slog.w(TAG, "overlay package " + packageName + " was upgraded, but couldn't be found");
+ onOverlayPackageRemoved(packageName, userId);
+ return;
+ }
+
+ try {
+ final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
+ if (!oldOi.targetPackageName.equals(pkg.overlayTarget)) {
+ mSettings.init(packageName, userId, pkg.overlayTarget,
+ pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(),
+ pkg.overlayPriority);
+ }
+
+ if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
+ mListener.onOverlaysChanged(pkg.overlayTarget, userId);
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ }
}
void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -333,10 +382,8 @@
try {
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(oi.targetPackageName, userId);
boolean modified = mSettings.setEnabled(packageName, userId, enable);
- modified |= updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
if (modified) {
mListener.onOverlaysChanged(oi.targetPackageName, userId);
@@ -349,7 +396,8 @@
boolean setEnabledExclusive(@NonNull final String packageName, final int userId) {
if (DEBUG) {
- Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName, userId));
+ Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName,
+ userId));
}
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
@@ -359,10 +407,9 @@
try {
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- final PackageInfo targetPackage =
- mPackageManager.getPackageInfo(oi.targetPackageName, userId);
+ final String targetPackageName = oi.targetPackageName;
- List<OverlayInfo> allOverlays = getOverlayInfosForTarget(oi.targetPackageName, userId);
+ List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId);
boolean modified = false;
@@ -384,15 +431,15 @@
// Disable the overlay.
modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
- modified |= updateState(targetPackage, disabledOverlayPackageInfo, userId);
+ modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0);
}
// Enable the selected overlay.
modified |= mSettings.setEnabled(packageName, userId, true);
- modified |= updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(targetPackageName, packageName, userId, 0);
if (modified) {
- mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ mListener.onOverlaysChanged(targetPackageName, userId);
}
return true;
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -477,7 +524,8 @@
List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
final int userId) {
- final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, userId);
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+ userId);
final List<String> paths = new ArrayList<>(overlays.size());
final int N = overlays.size();
for (int i = 0; i < N; i++) {
@@ -492,36 +540,59 @@
/**
* Returns true if the settings/state was modified, false otherwise.
*/
- private boolean updateState(@Nullable final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, final int userId)
+ private boolean updateState(@NonNull final String targetPackageName,
+ @NonNull final String overlayPackageName, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
+
+ final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
+ userId);
+
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
- if (targetPackage != null &&
- !("android".equals(targetPackage.packageName)
+ if (targetPackage != null && overlayPackage != null &&
+ !("android".equals(targetPackageName)
&& overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
- boolean modified = mSettings.setBaseCodePath(overlayPackage.packageName, userId,
- overlayPackage.applicationInfo.getBaseCodePath());
+ boolean modified = false;
+ if (overlayPackage != null) {
+ modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
+ overlayPackage.applicationInfo.getBaseCodePath());
+ }
- final int currentState = mSettings.getState(overlayPackage.packageName, userId);
- final int newState = calculateNewState(targetPackage, overlayPackage, userId);
+ final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
+ final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
+ userId, flags);
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackage.packageName, userId,
+ overlayPackageName, userId,
OverlayInfo.stateToString(currentState),
OverlayInfo.stateToString(newState)));
}
- modified |= mSettings.setState(overlayPackage.packageName, userId, newState);
+ modified |= mSettings.setState(overlayPackageName, userId, newState);
}
return modified;
}
- private int calculateNewState(@Nullable final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, final int userId)
+ private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
+ @Nullable final PackageInfo overlayPackage, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
+
+ if ((flags & FLAG_TARGET_IS_UPGRADING) != 0) {
+ return STATE_TARGET_UPGRADING;
+ }
+
+ if ((flags & FLAG_OVERLAY_IS_UPGRADING) != 0) {
+ return STATE_OVERLAY_UPGRADING;
+ }
+
+ // assert expectation on overlay package: can only be null if the flags are used
+ if (DEBUG && overlayPackage == null) {
+ throw new IllegalArgumentException("null overlay package not compatible with no flags");
+ }
+
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 17b38de..a80cae4 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -145,7 +145,8 @@
return mItems.get(idx).setEnabled(enable);
}
- int getState(@NonNull final String packageName, final int userId) throws BadKeyException {
+ @OverlayInfo.State int getState(@NonNull final String packageName, final int userId)
+ throws BadKeyException {
final int idx = select(packageName, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
@@ -156,8 +157,8 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setState(@NonNull final String packageName, final int userId, final int state)
- throws BadKeyException {
+ boolean setState(@NonNull final String packageName, final int userId,
+ final @OverlayInfo.State int state) throws BadKeyException {
final int idx = select(packageName, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
@@ -413,7 +414,7 @@
private final String mPackageName;
private final String mTargetPackageName;
private String mBaseCodePath;
- private int mState;
+ private @OverlayInfo.State int mState;
private boolean mIsEnabled;
private OverlayInfo mCache;
private boolean mIsStatic;
@@ -421,7 +422,7 @@
SettingsItem(@NonNull final String packageName, final int userId,
@NonNull final String targetPackageName, @NonNull final String baseCodePath,
- final int state, final boolean isEnabled, final boolean isStatic,
+ final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic,
final int priority) {
mPackageName = packageName;
mUserId = userId;
@@ -462,11 +463,11 @@
return false;
}
- private int getState() {
+ private @OverlayInfo.State int getState() {
return mState;
}
- private boolean setState(final int state) {
+ private boolean setState(final @OverlayInfo.State int state) {
if (mState != state) {
mState = state;
invalidateCache();
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 30088dd..fb81ebf 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -274,7 +274,7 @@
}
// Propagate permissions before removing any state
- propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
+ propagateInstantAppPermissionsIfNeeded(pkg, userId);
// Track instant apps
if (ps.getInstantApp(userId)) {
@@ -869,10 +869,10 @@
return uninstalledApps;
}
- private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
+ private void propagateInstantAppPermissionsIfNeeded(@NonNull PackageParser.Package pkg,
@UserIdInt int userId) {
InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
- packageName, userId);
+ pkg.packageName, userId);
if (appInfo == null) {
return;
}
@@ -884,8 +884,8 @@
for (String grantedPermission : appInfo.getGrantedPermissions()) {
final boolean propagatePermission =
mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
- if (propagatePermission) {
- mService.grantRuntimePermission(packageName, grantedPermission, userId);
+ if (propagatePermission && pkg.requestedPermissions.contains(grantedPermission)) {
+ mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
}
}
} finally {
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index af446ba..bc9fa4b 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -126,17 +126,16 @@
final Intent origIntent = requestObj.origIntent;
final Intent sanitizedIntent = sanitizeIntent(origIntent);
- final InstantAppDigest digest = getInstantAppDigest(origIntent);
- final int[] shaPrefix = digest.getDigestPrefix();
AuxiliaryResolveInfo resolveInfo = null;
@ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
try {
final List<InstantAppResolveInfo> instantAppResolveInfoList =
- connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token);
+ connection.getInstantAppResolveInfoList(sanitizedIntent,
+ requestObj.digest.getDigestPrefixSecure(), token);
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
resolveInfo = InstantAppResolver.filterInstantAppIntent(
instantAppResolveInfoList, origIntent, requestObj.resolvedType,
- requestObj.userId, origIntent.getPackage(), digest, token);
+ requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
}
} catch (ConnectionException e) {
if (e.failure == ConnectionException.FAILURE_BIND) {
@@ -166,12 +165,6 @@
return resolveInfo;
}
- private static InstantAppDigest getInstantAppDigest(Intent origIntent) {
- return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())
- ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/)
- : InstantAppDigest.UNDEFINED;
- }
-
public static void doInstantAppResolutionPhaseTwo(Context context,
InstantAppResolverConnection connection, InstantAppRequest requestObj,
ActivityInfo instantAppInstaller, Handler callbackHandler) {
@@ -182,8 +175,6 @@
}
final Intent origIntent = requestObj.origIntent;
final Intent sanitizedIntent = sanitizeIntent(origIntent);
- final InstantAppDigest digest = getInstantAppDigest(origIntent);
- final int[] shaPrefix = digest.getDigestPrefix();
final PhaseTwoCallback callback = new PhaseTwoCallback() {
@Override
@@ -194,7 +185,8 @@
final AuxiliaryResolveInfo instantAppIntentInfo =
InstantAppResolver.filterInstantAppIntent(
instantAppResolveInfoList, origIntent, null /*resolvedType*/,
- 0 /*userId*/, origIntent.getPackage(), digest, token);
+ 0 /*userId*/, origIntent.getPackage(), requestObj.digest,
+ token);
if (instantAppIntentInfo != null) {
failureIntent = instantAppIntentInfo.failureIntent;
} else {
@@ -225,8 +217,9 @@
}
};
try {
- connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback,
- callbackHandler, startTime);
+ connection.getInstantAppIntentFilterList(sanitizedIntent,
+ requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler,
+ startTime);
} catch (ConnectionException e) {
@ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
if (e.failure == ConnectionException.FAILURE_BIND) {
@@ -377,7 +370,7 @@
failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
failureIntent.setLaunchToken(token);
ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
- boolean isWebIntent = origIntent.isBrowsableWebIntent();
+ boolean isWebIntent = origIntent.isWebIntent();
for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) {
Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
@@ -448,7 +441,7 @@
instantAppInfo.getIntentFilters();
if (instantAppFilters == null || instantAppFilters.isEmpty()) {
// No filters on web intent; no matches, 2nd phase unnecessary.
- if (origIntent.isBrowsableWebIntent()) {
+ if (origIntent.isWebIntent()) {
return null;
}
// No filters; we need to start phase two
diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
index a9ee411..98f421e 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
@@ -141,6 +141,7 @@
}
}
+ @GuardedBy("mLock")
private void waitForBindLocked(String token) throws TimeoutException, InterruptedException {
final long startMillis = SystemClock.uptimeMillis();
while (mBindState != STATE_IDLE) {
@@ -250,6 +251,7 @@
}
}
+ @GuardedBy("mLock")
private void handleBinderDiedLocked() {
if (mRemoteInstance != null) {
try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 59f9dae..0b32d1a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -226,6 +226,7 @@
}
}
+ @GuardedBy("mSessions")
private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) {
final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
final ArraySet<File> unclaimedStages = newArraySet(
@@ -283,6 +284,7 @@
}
}
+ @GuardedBy("mSessions")
private void readSessionsLocked() {
if (LOGD) Slog.v(TAG, "readSessionsLocked()");
@@ -340,6 +342,7 @@
}
}
+ @GuardedBy("mSessions")
private void addHistoricalSessionLocked(PackageInstallerSession session) {
CharArrayWriter writer = new CharArrayWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -352,6 +355,7 @@
mHistoricalSessionsByInstaller.get(installerUid) + 1);
}
+ @GuardedBy("mSessions")
private void writeSessionsLocked() {
if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
@@ -612,6 +616,7 @@
}
}
+ @GuardedBy("mSessions")
private int allocateSessionIdLocked() {
int n = 0;
int sessionId;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3dd5a34..9c69281 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -318,6 +318,7 @@
/**
* @return {@code true} iff the installing is app an device owner or affiliated profile owner.
*/
+ @GuardedBy("mLock")
private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() {
DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
@@ -334,6 +335,7 @@
*
* @return {@code true} iff we need to ask to confirm the permissions?
*/
+ @GuardedBy("mLock")
private boolean needToAskForPermissionsLocked() {
if (mPermissionsManuallyAccepted) {
return false;
@@ -456,6 +458,7 @@
}
}
+ @GuardedBy("mLock")
private void assertPreparedAndNotSealedLocked(String cookie) {
assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
if (mSealed) {
@@ -463,6 +466,7 @@
}
}
+ @GuardedBy("mLock")
private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
assertPreparedAndNotDestroyedLocked(cookie);
if (mCommitted) {
@@ -470,6 +474,7 @@
}
}
+ @GuardedBy("mLock")
private void assertPreparedAndNotDestroyedLocked(String cookie) {
if (!mPrepared) {
throw new IllegalStateException(cookie + " before prepared");
@@ -484,6 +489,7 @@
* might point at an ASEC mount point, which is why we delay path resolution
* until someone actively works with the session.
*/
+ @GuardedBy("mLock")
private File resolveStageDirLocked() throws IOException {
if (mResolvedStageDir == null) {
if (stageDir != null) {
@@ -516,6 +522,7 @@
}
}
+ @GuardedBy("mLock")
private void computeProgressLocked(boolean forcePublish) {
mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
+ MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
@@ -728,6 +735,7 @@
* Check if the caller is the owner of this session. Otherwise throw a
* {@link SecurityException}.
*/
+ @GuardedBy("mLock")
private void assertCallerIsOwnerOrRootLocked() {
final int callingUid = Binder.getCallingUid();
if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid) {
@@ -738,6 +746,7 @@
/**
* If anybody is reading or writing data of the session, throw an {@link SecurityException}.
*/
+ @GuardedBy("mLock")
private void assertNoWriteFileTransfersOpenLocked() {
// Verify that all writers are hands-off
for (RevocableFileDescriptor fd : mFds) {
@@ -820,6 +829,7 @@
* @throws PackageManagerException if the session was sealed but something went wrong. If the
* session was sealed this is the only possible exception.
*/
+ @GuardedBy("mLock")
private void sealAndValidateLocked() throws PackageManagerException, IOException {
assertNoWriteFileTransfersOpenLocked();
assertPreparedAndNotDestroyedLocked("sealing of session");
@@ -901,6 +911,7 @@
mCallback.onSessionSealedBlocking(this);
}
+ @GuardedBy("mLock")
private void commitLocked()
throws PackageManagerException {
if (mDestroyed) {
@@ -1016,6 +1027,7 @@
* Note that upgrade compatibility is still performed by
* {@link PackageManagerService}.
*/
+ @GuardedBy("mLock")
private void validateInstallLocked(@Nullable PackageInfo pkgInfo)
throws PackageManagerException {
mPackageName = null;
@@ -1228,6 +1240,7 @@
}
}
+ @GuardedBy("mLock")
private void assertApkConsistentLocked(String tag, ApkLite apk)
throws PackageManagerException {
if (!mPackageName.equals(apk.packageName)) {
@@ -1511,6 +1524,7 @@
}
}
+ @GuardedBy("mLock")
private void dumpLocked(IndentingPrintWriter pw) {
pw.println("Session " + sessionId + ":");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 940d19f..384b074 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -797,6 +797,7 @@
return overlayPackages;
}
+ @GuardedBy("mInstallLock")
final String[] getStaticOverlayPathsLocked(Collection<PackageParser.Package> allPackages,
String targetPackageName, String targetPath) {
if ("android".equals(targetPackageName)) {
@@ -2746,12 +2747,6 @@
mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()
|| disabledPs.pkg == null) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Possibly deleted app: " + ps.dumpState_temp()
- + "; path: " + (disabledPs.codePath == null ? "<<NULL>>":disabledPs.codePath)
- + "; pkg: " + (disabledPs.pkg==null?"<<NULL>>":disabledPs.pkg.toString()));
-}
possiblyDeletedUpdatedSystemApps.add(ps.name);
}
}
@@ -2803,10 +2798,6 @@
for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
mSettings.removeDisabledSystemPackageLPw(deletedAppName);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "remove update; name: " + deletedAppName + ", exists? " + (deletedPkg != null));
-}
final String msg;
if (deletedPkg == null) {
// should have found an update, but, we didn't; remove everything
@@ -5982,7 +5973,7 @@
if (!skipPackageCheck && intent.getPackage() != null) {
return false;
}
- if (!intent.isBrowsableWebIntent()) {
+ if (!intent.isWebIntent()) {
// for non web intents, we should not resolve externally if an app already exists to
// handle it or if the caller didn't explicitly request it.
if ((resolvedActivities != null && resolvedActivities.size() != 0)
@@ -6693,7 +6684,7 @@
ai.packageName, ai.versionCode, null /* splitName */);
}
}
- if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) {
+ if (intent.isWebIntent() && auxiliaryResponse == null) {
return result;
}
final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName);
@@ -8534,8 +8525,6 @@
return false;
}
- // Temporary to catch potential issues with refactoring
- private static boolean REFACTOR_DEBUG = true;
/**
* Adds a new package to the internal data structures during platform initialization.
* <p>After adding, the package is known to the system and available for querying.
@@ -8576,10 +8565,6 @@
synchronized (mPackages) {
renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
final String realPkgName = getRealPackageName(pkg, renamedPkgName);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Add pkg: " + pkg.packageName + (realPkgName==null?"":", realName: " + realPkgName));
-}
if (realPkgName != null) {
ensurePackageRenamed(pkg, renamedPkgName);
}
@@ -8594,12 +8579,6 @@
if (DEBUG_INSTALL && isSystemPkgUpdated) {
Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
}
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "SSP? " + scanSystemPartition
- + ", exists? " + pkgAlreadyExists + (pkgAlreadyExists?" "+pkgSetting.toString():"")
- + ", upgraded? " + isSystemPkgUpdated + (isSystemPkgUpdated?" "+disabledPkgSetting.toString():""));
-}
final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null)
? mSettings.getSharedUserLPw(pkg.mSharedUserId,
@@ -8611,12 +8590,6 @@
Log.d(TAG, "Shared UserID " + pkg.mSharedUserId
+ " (uid=" + sharedUserSetting.userId + "):"
+ " packages=" + sharedUserSetting.packages);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Shared UserID " + pkg.mSharedUserId
- + " (uid=" + sharedUserSetting.userId + "):"
- + " packages=" + sharedUserSetting.packages);
-}
}
if (scanSystemPartition) {
@@ -8625,10 +8598,6 @@
// version on /data, cycle through all of its children packages and
// remove children that are no longer defined.
if (isSystemPkgUpdated) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Disable child packages");
-}
final int scannedChildCount = (pkg.childPackages != null)
? pkg.childPackages.size() : 0;
final int disabledChildCount = disabledPkgSetting.childPackageNames != null
@@ -8640,19 +8609,11 @@
for (int j = 0; j < scannedChildCount; j++) {
PackageParser.Package childPkg = pkg.childPackages.get(j);
if (childPkg.packageName.equals(disabledChildPackageName)) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Ignore " + disabledChildPackageName);
-}
disabledPackageAvailable = true;
break;
}
}
if (!disabledPackageAvailable) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Disable " + disabledChildPackageName);
-}
mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
}
}
@@ -8661,44 +8622,17 @@
disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */,
null /* originalPkgSetting */, null, parseFlags, scanFlags,
(pkg == mPlatformPackage), user);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Scan disabled system package");
-Slog.e("TODD",
- "Pre: " + request.pkgSetting.dumpState_temp());
-}
-final ScanResult result =
scanPackageOnlyLI(request, mFactoryTest, -1L);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Post: " + (result.success?result.pkgSetting.dumpState_temp():"FAILED scan"));
-}
}
}
}
final boolean newPkgChangedPaths =
pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "paths changed? " + newPkgChangedPaths
- + "; old: " + pkg.codePath
- + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.codePathString));
-}
final boolean newPkgVersionGreater =
pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode;
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "version greater? " + newPkgVersionGreater
- + "; old: " + pkg.getLongVersionCode()
- + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.versionCode));
-}
final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
&& newPkgChangedPaths && newPkgVersionGreater;
-if (REFACTOR_DEBUG) {
- Slog.e("TODD",
- "system better? " + isSystemPkgBetter);
-}
if (isSystemPkgBetter) {
// The version of the application on /system is greater than the version on
// /data. Switch back to the application on /system.
@@ -8714,13 +8648,6 @@
+ " name: " + pkgSetting.name
+ "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+ "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package changed;"
- + " name: " + pkgSetting.name
- + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
- + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-}
final InstallArgs args = createInstallArgsForExisting(
packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
@@ -8732,10 +8659,6 @@
}
if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "THROW exception; system pkg version not good enough");
-}
// The version of the application on the /system partition is less than or
// equal to the version on the /data partition. Throw an exception and use
// the application already installed on the /data partition.
@@ -8766,11 +8689,6 @@
logCriticalInfo(Log.WARN,
"System package signature mismatch;"
+ " name: " + pkgSetting.name);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package signature mismatch;"
- + " name: " + pkgSetting.name);
-}
try (PackageFreezer freezer = freezePackage(pkg.packageName,
"scanPackageInternalLI")) {
deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
@@ -8785,13 +8703,6 @@
+ " name: " + pkgSetting.name
+ "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+ "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package enabled;"
- + " name: " + pkgSetting.name
- + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
- + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
-}
InstallArgs args = createInstallArgsForExisting(
packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
@@ -8808,35 +8719,13 @@
+ " name: " + pkgSetting.name
+ "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
+ "; new: " + pkg.codePath + " @ " + pkg.codePath);
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "System package disabled;"
- + " name: " + pkgSetting.name
- + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
- + "; new: " + pkg.codePath + " @ " + pkg.codePath);
-}
}
}
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Scan package");
-Slog.e("TODD",
- "Pre: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp()));
-}
final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
-if (REFACTOR_DEBUG) {
-pkgSetting = mSettings.getPackageLPr(pkg.packageName);
-Slog.e("TODD",
- "Post: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp()));
-}
if (shouldHideSystemApp) {
-if (REFACTOR_DEBUG) {
-Slog.e("TODD",
- "Disable package: " + pkg.packageName);
-}
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(pkg.packageName, true);
}
@@ -9127,6 +9016,7 @@
}
}
+ @GuardedBy("mPackages")
private void notifyPackageUseLocked(String packageName, int reason) {
final PackageParser.Package p = mPackages.get(packageName);
if (p == null) {
@@ -10737,8 +10627,6 @@
~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
pkg.applicationInfo.privateFlags &=
~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
- // clear protected broadcasts
- pkg.protectedBroadcasts = null;
// cap permission priorities
if (pkg.permissionGroups != null && pkg.permissionGroups.size() > 0) {
for (int i = pkg.permissionGroups.size() - 1; i >= 0; --i) {
@@ -10747,6 +10635,8 @@
}
}
if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
+ // clear protected broadcasts
+ pkg.protectedBroadcasts = null;
// ignore export request for single user receivers
if (pkg.receivers != null) {
for (int i = pkg.receivers.size() - 1; i >= 0; --i) {
@@ -14066,6 +13956,7 @@
}
}
+ @GuardedBy("mPackages")
private boolean canSuspendPackageForUserLocked(String packageName, int userId) {
if (isPackageDeviceAdmin(packageName, userId)) {
Slog.w(TAG, "Cannot suspend/un-suspend package \"" + packageName
@@ -16722,6 +16613,12 @@
if (userId != UserHandle.USER_ALL) {
ps.setInstalled(true, userId);
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+ } else {
+ for (int currentUserId : sUserManager.getUserIds()) {
+ ps.setInstalled(true, currentUserId);
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
+ installerPackageName);
+ }
}
// When replacing an existing package, preserve the original install reason for all
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3e2bd4a..ea05b74 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -97,35 +97,6 @@
+ " " + name + "/" + appId + "}";
}
- // Temporary to catch potential issues with refactoring
- public String dumpState_temp() {
- String flags = "";
- flags += ((pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
- flags += ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
- if ("".equals(flags)) {
- flags = "-";
- }
- String privFlags = "";
- privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
- privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
- privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
- if ("".equals(privFlags)) {
- privFlags = "-";
- }
- return "PackageSetting{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + name + (realName == null ? "" : "("+realName+")") + "/" + appId + (sharedUser==null?"":" u:" + sharedUser.name+"("+sharedUserId+")")
- + ", ver:" + versionCode
- + ", path: " + codePath
- + ", pABI: " + primaryCpuAbiString
- + ", sABI: " + secondaryCpuAbiString
- + ", oABI: " + cpuAbiOverrideString
- + ", flags: " + flags
- + ", privFlags: " + privFlags
- + ", pkg: " + (pkg == null ? "<<NULL>>" : pkg.dumpState_temp())
- + "}";
- }
-
public void copyFrom(PackageSetting orig) {
super.copyFrom(orig);
doCopy(orig);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a85d6d8..034fd23 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -530,6 +530,7 @@
return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
}
+ @GuardedBy("mLock")
boolean isUidForegroundLocked(int uid) {
if (uid == Process.SYSTEM_UID) {
// IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
@@ -545,6 +546,7 @@
return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid));
}
+ @GuardedBy("mLock")
long getUidLastForegroundElapsedTimeLocked(int uid) {
return mUidLastForegroundElapsedTime.get(uid);
}
@@ -638,6 +640,7 @@
}
}
+ @GuardedBy("mLock")
private void unloadUserLocked(int userId) {
if (DEBUG) {
Slog.d(TAG, "unloadUserLocked: user=" + userId);
@@ -864,6 +867,7 @@
writeAttr(out, name, intent.toUri(/* flags =*/ 0));
}
+ @GuardedBy("mLock")
@VisibleForTesting
void saveBaseStateLocked() {
final AtomicFile file = getBaseStateFile();
@@ -896,6 +900,7 @@
}
}
+ @GuardedBy("mLock")
private void loadBaseStateLocked() {
mRawLastResetTime = 0;
@@ -948,6 +953,7 @@
return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
}
+ @GuardedBy("mLock")
private void saveUserLocked(@UserIdInt int userId) {
final File path = getUserFile(userId);
if (DEBUG) {
@@ -974,6 +980,7 @@
}
}
+ @GuardedBy("mLock")
private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
boolean forBackup) throws IOException, XmlPullParserException {
@@ -1107,12 +1114,14 @@
}
/** Return the last reset time. */
+ @GuardedBy("mLock")
long getLastResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime;
}
/** Return the next reset time. */
+ @GuardedBy("mLock")
long getNextResetTimeLocked() {
updateTimesLocked();
return mRawLastResetTime + mResetInterval;
@@ -1125,6 +1134,7 @@
/**
* Update the last reset time.
*/
+ @GuardedBy("mLock")
private void updateTimesLocked() {
final long now = injectCurrentTimeMillis();
@@ -1220,6 +1230,7 @@
return ret;
}
+ @GuardedBy("mLock")
void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
for (int i = mUsers.size() - 1; i >= 0; i--) {
c.accept(mUsers.valueAt(i));
@@ -1279,6 +1290,7 @@
* {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
* saves are going on.
*/
+ @GuardedBy("mLock")
private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
@@ -2108,6 +2120,7 @@
}
}
+ @GuardedBy("mLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
@@ -2418,6 +2431,7 @@
*
* This is called when an app is uninstalled, or an app gets "clear data"ed.
*/
+ @GuardedBy("mLock")
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
boolean appStillExists) {
@@ -2508,6 +2522,7 @@
return setReturnedByServer(ret);
}
+ @GuardedBy("ShortcutService.this.mLock")
private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
@Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
@Nullable ComponentName componentName, int queryFlags,
@@ -2579,6 +2594,7 @@
}
}
+ @GuardedBy("ShortcutService.this.mLock")
private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId,
@@ -2940,6 +2956,7 @@
verifyStates();
}
+ @GuardedBy("mLock")
private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
final ShortcutUser user = getUserShortcutsLocked(userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b53d83b..a0577b1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1547,6 +1547,7 @@
return result;
}
+ @GuardedBy("mRestrictionsLock")
private EnforcingUser getEnforcingUserLocked(@UserIdInt int userId) {
int source = mDeviceOwnerUserId == userId ? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER
: UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
@@ -2896,6 +2897,7 @@
}
}
+ @GuardedBy("mUsersLock")
@VisibleForTesting
void addRemovingUserIdLocked(int userId) {
// We remember deleted user IDs to prevent them from being
@@ -3405,6 +3407,7 @@
return nextId;
}
+ @GuardedBy("mUsersLock")
private int scanNextAvailableIdLocked() {
for (int i = MIN_USER_ID; i < MAX_USER_ID; i++) {
if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 842f8d0..23185d7 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -122,7 +122,8 @@
UserManager.DISALLOW_CONFIG_BRIGHTNESS,
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
UserManager.DISALLOW_AMBIENT_DISPLAY,
- UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT
+ UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT,
+ UserManager.DISALLOW_PRINTING
});
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 08f8bbd..3f8a1e8 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1210,6 +1210,7 @@
return false;
}
+ @GuardedBy("mLock")
private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
if (pkg.parentPackage == null) {
@@ -1499,6 +1500,7 @@
}
}
+ @GuardedBy("mLock")
private int[] revokeUnusedSharedUserPermissionsLocked(
SharedUserSetting suSetting, int[] allUserIds) {
// Collect all used permissions in the UID
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
index f6c4990..b3f2833 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -201,34 +201,42 @@
}
}
+ @GuardedBy("mLock")
@Nullable BasePermission getPermissionLocked(@NonNull String permName) {
return mPermissions.get(permName);
}
+ @GuardedBy("mLock")
@Nullable BasePermission getPermissionTreeLocked(@NonNull String permName) {
return mPermissionTrees.get(permName);
}
+ @GuardedBy("mLock")
void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
mPermissions.put(permName, permission);
}
+ @GuardedBy("mLock")
void putPermissionTreeLocked(@NonNull String permName, @NonNull BasePermission permission) {
mPermissionTrees.put(permName, permission);
}
+ @GuardedBy("mLock")
void removePermissionLocked(@NonNull String permName) {
mPermissions.remove(permName);
}
+ @GuardedBy("mLock")
void removePermissionTreeLocked(@NonNull String permName) {
mPermissionTrees.remove(permName);
}
+ @GuardedBy("mLock")
@NonNull Collection<BasePermission> getAllPermissionsLocked() {
return mPermissions.values();
}
+ @GuardedBy("mLock")
@NonNull Collection<BasePermission> getAllPermissionTreesLocked() {
return mPermissionTrees.values();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 177d6af..0502848 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5603,7 +5603,9 @@
final int fl = PolicyControl.getWindowFlags(null,
mTopFullscreenOpaqueWindowState.getAttrs());
if (localLOGV) {
- Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw());
+ Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
+ + " shown position: "
+ + mTopFullscreenOpaqueWindowState.getShownPositionLw());
Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
+ " lp.flags=0x" + Integer.toHexString(fl));
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3af3fcb..e9c4c5c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -232,6 +232,14 @@
public Rect getFrameLw();
/**
+ * Retrieve the current position of the window that is actually shown.
+ * Must be called with the window manager lock held.
+ *
+ * @return Point The point holding the shown window position.
+ */
+ public Point getShownPositionLw();
+
+ /**
* Retrieve the frame of the display that this window was last
* laid out in. Must be called with the
* window manager lock held.
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 847c90a..08dc97e 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -306,6 +306,7 @@
}
}
+ @GuardedBy("mLock")
@VisibleForTesting
void updateConstantsLocked(final String setting, final String deviceSpecificSetting) {
mSettings = setting;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index a536270..3072f21 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -85,6 +85,7 @@
private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4;
private static final int MSG_PROFILE_TIMED_OUT = 5;
+ private static final int MSG_WIRED_CHARGING_STARTED = 6;
private final Object mLock = new Object();
@@ -571,6 +572,20 @@
mHandler.sendMessage(msg);
}
+ /**
+ * Called when wired charging has started so as to provide user feedback
+ */
+ public void onWiredChargingStarted() {
+ if (DEBUG) {
+ Slog.d(TAG, "onWiredChargingStarted");
+ }
+
+ mSuspendBlocker.acquire();
+ Message msg = mHandler.obtainMessage(MSG_WIRED_CHARGING_STARTED);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
private void updatePendingBroadcastLocked() {
if (!mBroadcastInProgress
&& mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN
@@ -703,11 +718,14 @@
}
};
- private void playWirelessChargingStartedSound() {
+ /**
+ * Plays the wireless charging sound for both wireless and non-wireless charging
+ */
+ private void playChargingStartedSound() {
final boolean enabled = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.CHARGING_SOUNDS_ENABLED, 1) != 0;
final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.WIRELESS_CHARGING_STARTED_SOUND);
+ Settings.Global.CHARGING_STARTED_SOUND);
if (enabled && soundPath != null) {
final Uri soundUri = Uri.parse("file://" + soundPath);
if (soundUri != null) {
@@ -721,11 +739,16 @@
}
private void showWirelessChargingStarted(int batteryLevel) {
- playWirelessChargingStartedSound();
+ playChargingStartedSound();
mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
mSuspendBlocker.release();
}
+ private void showWiredChargingStarted() {
+ playChargingStartedSound();
+ mSuspendBlocker.release();
+ }
+
private void lockProfile(@UserIdInt int userId) {
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
@@ -753,6 +776,8 @@
case MSG_PROFILE_TIMED_OUT:
lockProfile(msg.arg1);
break;
+ case MSG_WIRED_CHARGING_STARTED:
+ showWiredChargingStarted();
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index db83158..38dc820 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -120,9 +120,6 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_SPEW = DEBUG && true;
- // if DEBUG_WIRELESS=true, plays wireless charging animation w/ sound on every plug + unplug
- private static final boolean DEBUG_WIRELESS = false;
-
// Message: Sent when a user activity timeout occurs to update the power state.
private static final int MSG_USER_ACTIVITY_TIMEOUT = 1;
// Message: Sent when the device enters or exits a dreaming or dozing state.
@@ -1743,10 +1740,15 @@
userActivityNoUpdateLocked(
now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
- // Tell the notifier whether wireless charging has started so that
- // it can provide feedback to the user.
- if (dockedOnWirelessCharger || DEBUG_WIRELESS) {
- mNotifier.onWirelessChargingStarted(mBatteryLevel);
+ // only play charging sounds if boot is completed so charging sounds don't play
+ // with potential notification sounds
+ if (mBootCompleted) {
+ if (mIsPowered && !BatteryManager.isPlugWired(oldPlugType)
+ && BatteryManager.isPlugWired(mPlugType)) {
+ mNotifier.onWiredChargingStarted();
+ } else if (dockedOnWirelessCharger) {
+ mNotifier.onWirelessChargingStarted(mBatteryLevel);
+ }
}
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
index b0b07ea..37df94f 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -285,6 +285,7 @@
}
}
+ @GuardedBy("mLock")
private void transitionStateLocked(int newState) {
if (mCurrentState == newState) {
return;
@@ -298,6 +299,7 @@
mMetricsLoggerHelper.transitionState(newState, now, batteryLevel, batteryPercent);
}
+ @GuardedBy("mLock")
private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) {
if (mCurrentState < 0) {
return;
@@ -338,6 +340,7 @@
}
+ @GuardedBy("mLock")
private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) {
if (DEBUG) {
Slog.d(TAG, "New state: " + stateToString(newState));
@@ -363,7 +366,7 @@
indent = indent + " ";
pw.print(indent);
- pw.println("Battery Saver: Off On");
+ pw.println("Battery Saver: w/Off w/On");
dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
DozeState.NOT_DOZING, "NonDoze");
dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr",
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index e0ab9e9..c08b610 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -306,6 +306,7 @@
}
}
+ @GuardedBy("mLock")
private void saveDefaultValuesLocked() {
final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
@@ -334,6 +335,7 @@
}
}
+ @GuardedBy("mLock")
@VisibleForTesting
boolean loadDefaultValuesLocked() {
final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index c280739..ef6067a 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -85,11 +85,9 @@
public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
static final String TAG = "StatsCompanionService";
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
- public static final String ACTION_TRIGGER_COLLECTION =
- "com.android.server.stats.action.TRIGGER_COLLECTION";
-
+ public static final int CODE_DATA_BROADCAST = 1;
public static final int CODE_SUBSCRIBER_BROADCAST = 1;
private final Context mContext;
@@ -110,9 +108,9 @@
private TelephonyManager mTelephony = null;
private final StatFs mStatFsData = new StatFs(Environment.getDataDirectory().getAbsolutePath());
private final StatFs mStatFsSystem =
- new StatFs(Environment.getRootDirectory().getAbsolutePath());
+ new StatFs(Environment.getRootDirectory().getAbsolutePath());
private final StatFs mStatFsTemp =
- new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath());
+ new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath());
public StatsCompanionService(Context context) {
super();
@@ -122,7 +120,7 @@
mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
new Intent(mContext, AnomalyAlarmReceiver.class), 0);
mPullingAlarmIntent = PendingIntent.getBroadcast(
- mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
+ mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
mAppUpdateReceiver = new AppUpdateReceiver();
mUserUpdateReceiver = new BroadcastReceiver() {
@Override
@@ -153,17 +151,21 @@
for (int i = 0; i < numClusters; i++) {
final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
- numSpeedSteps);
+ numSpeedSteps);
firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
}
}
@Override
- public void sendBroadcast(String pkg, String cls) {
- // TODO: Use a pending intent.
+ public void sendDataBroadcast(IBinder intentSenderBinder) {
enforceCallingPermission();
- mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls),
- UserHandle.SYSTEM);
+ IntentSender intentSender = new IntentSender(intentSenderBinder);
+ Intent intent = new Intent();
+ try {
+ intentSender.sendIntent(mContext, CODE_DATA_BROADCAST, intent, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.w(TAG, "Unable to send using IntentSender");
+ }
}
@Override
@@ -209,6 +211,7 @@
}
// Assumes that sStatsdLock is held.
+ @GuardedBy("sStatsdLock")
private final void informAllUidsLocked(Context context) throws RemoteException {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
PackageManager pm = context.getPackageManager();
@@ -284,7 +287,7 @@
}
}
- private final static class AnomalyAlarmReceiver extends BroadcastReceiver {
+ public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
@@ -304,25 +307,24 @@
}
}
- private final static class PullingAlarmReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG)
- Slog.d(TAG, "Time to poll something.");
- synchronized (sStatsdLock) {
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
- return;
- }
- try {
- // Two-way call to statsd to retain AlarmManager wakelock
- sStatsd.informPollAlarmFired();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
- }
+ public final static class PullingAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG)
+ Slog.d(TAG, "Time to poll something.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
+ return;
+ }
+ try {
+ // Two-way call to statsd to retain AlarmManager wakelock
+ sStatsd.informPollAlarmFired();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
+ }
+ }
}
- // AlarmManager releases its own wakelock here.
- }
}
private final static class ShutdownEventReceiver extends BroadcastReceiver {
@@ -332,9 +334,9 @@
* Skip immediately if intent is not relevant to device shutdown.
*/
if (!intent.getAction().equals(Intent.ACTION_REBOOT)
- && !(intent.getAction().equals(Intent.ACTION_SHUTDOWN)
- && (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0)) {
- return;
+ && !(intent.getAction().equals(Intent.ACTION_SHUTDOWN)
+ && (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0)) {
+ return;
}
Slog.i(TAG, "StatsCompanionService noticed a shutdown.");
@@ -381,50 +383,50 @@
@Override // Binder call
public void setPullingAlarms(long timestampMs, long intervalMs) {
- enforceCallingPermission();
- if (DEBUG)
- Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms");
- final long callingToken = Binder.clearCallingIdentity();
- try {
- // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
- // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
- // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
- mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, mPullingAlarmIntent);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+ // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+ // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+ mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, mPullingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
}
@Override // Binder call
public void cancelPullingAlarms() {
- enforceCallingPermission();
- if (DEBUG)
- Slog.d(TAG, "Cancelling pulling alarm");
- final long callingToken = Binder.clearCallingIdentity();
- try {
- mAlarmManager.cancel(mPullingAlarmIntent);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Cancelling pulling alarm");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.cancel(mPullingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
}
private void addNetworkStats(
- int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) {
- int size = stats.size();
- NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
- for (int j = 0; j < size; j++) {
- stats.getValues(j, entry);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tag, withFGBG ? 6 : 5);
- e.writeInt(entry.uid);
- if (withFGBG) {
- e.writeInt(entry.set);
+ int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) {
+ int size = stats.size();
+ NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+ for (int j = 0; j < size; j++) {
+ stats.getValues(j, entry);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tag, withFGBG ? 6 : 5);
+ e.writeInt(entry.uid);
+ if (withFGBG) {
+ e.writeInt(entry.set);
+ }
+ e.writeLong(entry.rxBytes);
+ e.writeLong(entry.rxPackets);
+ e.writeLong(entry.txBytes);
+ e.writeLong(entry.txPackets);
+ ret.add(e);
}
- e.writeLong(entry.rxBytes);
- e.writeLong(entry.rxPackets);
- e.writeLong(entry.txBytes);
- e.writeLong(entry.txPackets);
- ret.add(e);
- }
}
/**
@@ -491,220 +493,220 @@
}
private void pullKernelWakelock(int tagId, List<StatsLogEventWrapper> pulledData) {
- final KernelWakelockStats wakelockStats =
- mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
- for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
- String name = ent.getKey();
- KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 4);
- e.writeString(name);
- e.writeInt(kws.mCount);
- e.writeInt(kws.mVersion);
- e.writeLong(kws.mTotalTime);
- pulledData.add(e);
- }
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 4);
+ e.writeString(name);
+ e.writeInt(kws.mCount);
+ e.writeInt(kws.mVersion);
+ e.writeLong(kws.mTotalTime);
+ pulledData.add(e);
+ }
}
private void pullWifiBytesTransfer(int tagId, List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- // TODO: Consider caching the following call to get BatteryStatsInternal.
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getWifiIfaces();
- if (ifaces.length == 0) {
- return;
+ long token = Binder.clearCallingIdentity();
+ try {
+ // TODO: Consider caching the following call to get BatteryStatsInternal.
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats =
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null)
+ .groupedByUid();
+ addNetworkStats(tagId, pulledData, stats, false);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- NetworkStatsFactory nsf = new NetworkStatsFactory();
- // Combine all the metrics per Uid into one record.
- NetworkStats stats =
- nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null)
- .groupedByUid();
- addNetworkStats(tagId, pulledData, stats, false);
- } catch (java.io.IOException e) {
- Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
}
private void pullWifiBytesTransferByFgBg(int tagId, List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getWifiIfaces();
- if (ifaces.length == 0) {
- return;
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null));
+ addNetworkStats(tagId, pulledData, stats, true);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- NetworkStatsFactory nsf = new NetworkStatsFactory();
- NetworkStats stats = rollupNetworkStatsByFGBG(
- nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null));
- addNetworkStats(tagId, pulledData, stats, true);
- } catch (java.io.IOException e) {
- Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
}
private void pullMobileBytesTransfer(int tagId, List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getMobileIfaces();
- if (ifaces.length == 0) {
- return;
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats =
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null)
+ .groupedByUid();
+ addNetworkStats(tagId, pulledData, stats, false);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- NetworkStatsFactory nsf = new NetworkStatsFactory();
- // Combine all the metrics per Uid into one record.
- NetworkStats stats =
- nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null)
- .groupedByUid();
- addNetworkStats(tagId, pulledData, stats, false);
- } catch (java.io.IOException e) {
- Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
}
private void pullBluetoothBytesTransfer(int tagId, List<StatsLogEventWrapper> pulledData) {
- BluetoothActivityEnergyInfo info = pullBluetoothData();
- for (UidTraffic traffic : info.getUidTraffic()) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
- e.writeInt(traffic.getUid());
- e.writeLong(traffic.getRxBytes());
- e.writeLong(traffic.getTxBytes());
- pulledData.add(e);
- }
+ BluetoothActivityEnergyInfo info = pullBluetoothData();
+ for (UidTraffic traffic : info.getUidTraffic()) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
+ e.writeInt(traffic.getUid());
+ e.writeLong(traffic.getRxBytes());
+ e.writeLong(traffic.getTxBytes());
+ pulledData.add(e);
+ }
}
private void pullMobileBytesTransferByFgBg(int tagId, List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getMobileIfaces();
- if (ifaces.length == 0) {
- return;
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null));
+ addNetworkStats(tagId, pulledData, stats, true);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- NetworkStatsFactory nsf = new NetworkStatsFactory();
- NetworkStats stats = rollupNetworkStatsByFGBG(
- nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces, NetworkStats.TAG_NONE, null));
- addNetworkStats(tagId, pulledData, stats, true);
- } catch (java.io.IOException e) {
- Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
}
private void pullCpuTimePerFreq(int tagId, List<StatsLogEventWrapper> pulledData) {
- for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
- if (clusterTimeMs != null) {
- for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
- e.writeInt(cluster);
- e.writeInt(speed);
- e.writeLong(clusterTimeMs[speed]);
- pulledData.add(e);
- }
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
+ if (clusterTimeMs != null) {
+ for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
+ e.writeInt(cluster);
+ e.writeInt(speed);
+ e.writeLong(clusterTimeMs[speed]);
+ pulledData.add(e);
+ }
+ }
}
- }
}
private void pullWifiActivityEnergyInfo(int tagId, List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- if (mWifiManager == null) {
- mWifiManager =
- IWifiManager.Stub.asInterface(ServiceManager.getService(Context.WIFI_SERVICE));
- }
- if (mWifiManager != null) {
- try {
- SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
- mWifiManager.requestActivityInfo(wifiReceiver);
- final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 6);
- e.writeLong(wifiInfo.getTimeStamp());
- e.writeInt(wifiInfo.getStackState());
- e.writeLong(wifiInfo.getControllerTxTimeMillis());
- e.writeLong(wifiInfo.getControllerRxTimeMillis());
- e.writeLong(wifiInfo.getControllerIdleTimeMillis());
- e.writeLong(wifiInfo.getControllerEnergyUsed());
- pulledData.add(e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling wifiManager for wifi controller activity energy info has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
+ long token = Binder.clearCallingIdentity();
+ if (mWifiManager == null) {
+ mWifiManager =
+ IWifiManager.Stub.asInterface(ServiceManager.getService(Context.WIFI_SERVICE));
}
- }
+ if (mWifiManager != null) {
+ try {
+ SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
+ mWifiManager.requestActivityInfo(wifiReceiver);
+ final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 6);
+ e.writeLong(wifiInfo.getTimeStamp());
+ e.writeInt(wifiInfo.getStackState());
+ e.writeLong(wifiInfo.getControllerTxTimeMillis());
+ e.writeLong(wifiInfo.getControllerRxTimeMillis());
+ e.writeLong(wifiInfo.getControllerIdleTimeMillis());
+ e.writeLong(wifiInfo.getControllerEnergyUsed());
+ pulledData.add(e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling wifiManager for wifi controller activity energy info has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
private void pullModemActivityInfo(int tagId, List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- if (mTelephony == null) {
- mTelephony = TelephonyManager.from(mContext);
- }
- if (mTelephony != null) {
- SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
- mTelephony.requestModemActivityInfo(modemReceiver);
- final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 6);
- e.writeLong(modemInfo.getTimestamp());
- e.writeLong(modemInfo.getSleepTimeMillis());
- e.writeLong(modemInfo.getIdleTimeMillis());
- e.writeLong(modemInfo.getTxTimeMillis()[0]);
- e.writeLong(modemInfo.getTxTimeMillis()[1]);
- e.writeLong(modemInfo.getTxTimeMillis()[2]);
- e.writeLong(modemInfo.getTxTimeMillis()[3]);
- e.writeLong(modemInfo.getTxTimeMillis()[4]);
- e.writeLong(modemInfo.getRxTimeMillis());
- e.writeLong(modemInfo.getEnergyUsed());
- pulledData.add(e);
- }
+ long token = Binder.clearCallingIdentity();
+ if (mTelephony == null) {
+ mTelephony = TelephonyManager.from(mContext);
+ }
+ if (mTelephony != null) {
+ SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
+ mTelephony.requestModemActivityInfo(modemReceiver);
+ final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 6);
+ e.writeLong(modemInfo.getTimestamp());
+ e.writeLong(modemInfo.getSleepTimeMillis());
+ e.writeLong(modemInfo.getIdleTimeMillis());
+ e.writeLong(modemInfo.getTxTimeMillis()[0]);
+ e.writeLong(modemInfo.getTxTimeMillis()[1]);
+ e.writeLong(modemInfo.getTxTimeMillis()[2]);
+ e.writeLong(modemInfo.getTxTimeMillis()[3]);
+ e.writeLong(modemInfo.getTxTimeMillis()[4]);
+ e.writeLong(modemInfo.getRxTimeMillis());
+ e.writeLong(modemInfo.getEnergyUsed());
+ pulledData.add(e);
+ }
}
private void pullBluetoothActivityInfo(int tagId, List<StatsLogEventWrapper> pulledData) {
- BluetoothActivityEnergyInfo info = pullBluetoothData();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 6);
- e.writeLong(info.getTimeStamp());
- e.writeInt(info.getBluetoothStackState());
- e.writeLong(info.getControllerTxTimeMillis());
- e.writeLong(info.getControllerRxTimeMillis());
- e.writeLong(info.getControllerIdleTimeMillis());
- e.writeLong(info.getControllerEnergyUsed());
- pulledData.add(e);
+ BluetoothActivityEnergyInfo info = pullBluetoothData();
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 6);
+ e.writeLong(info.getTimeStamp());
+ e.writeInt(info.getBluetoothStackState());
+ e.writeLong(info.getControllerTxTimeMillis());
+ e.writeLong(info.getControllerRxTimeMillis());
+ e.writeLong(info.getControllerIdleTimeMillis());
+ e.writeLong(info.getControllerEnergyUsed());
+ pulledData.add(e);
}
private synchronized BluetoothActivityEnergyInfo pullBluetoothData() {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
- adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
- return awaitControllerInfo(bluetoothReceiver);
- } else {
- Slog.e(TAG, "Failed to get bluetooth adapter!");
- return null;
- }
+ if (adapter != null) {
+ SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
+ adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+ return awaitControllerInfo(bluetoothReceiver);
+ } else {
+ Slog.e(TAG, "Failed to get bluetooth adapter!");
+ return null;
+ }
}
private void pullSystemElapsedRealtime(int tagId, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1);
- e.writeLong(SystemClock.elapsedRealtime());
- pulledData.add(e);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1);
+ e.writeLong(SystemClock.elapsedRealtime());
+ pulledData.add(e);
}
private void pullDiskSpace(int tagId, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
- e.writeLong(mStatFsData.getAvailableBytes());
- e.writeLong(mStatFsSystem.getAvailableBytes());
- e.writeLong(mStatFsTemp.getAvailableBytes());
- pulledData.add(e);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
+ e.writeLong(mStatFsData.getAvailableBytes());
+ e.writeLong(mStatFsSystem.getAvailableBytes());
+ e.writeLong(mStatFsTemp.getAvailableBytes());
+ pulledData.add(e);
}
private void pullSystemUpTime(int tagId, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1);
- e.writeLong(SystemClock.uptimeMillis());
- pulledData.add(e);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1);
+ e.writeLong(SystemClock.uptimeMillis());
+ pulledData.add(e);
}
/**
@@ -718,56 +720,56 @@
List<StatsLogEventWrapper> ret = new ArrayList();
switch (tagId) {
case StatsLog.WIFI_BYTES_TRANSFER: {
- pullWifiBytesTransfer(tagId, ret);
- break;
+ pullWifiBytesTransfer(tagId, ret);
+ break;
}
case StatsLog.MOBILE_BYTES_TRANSFER: {
- pullMobileBytesTransfer(tagId, ret);
- break;
+ pullMobileBytesTransfer(tagId, ret);
+ break;
}
case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
- pullWifiBytesTransferByFgBg(tagId, ret);
- break;
+ pullWifiBytesTransferByFgBg(tagId, ret);
+ break;
}
case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
- pullMobileBytesTransferByFgBg(tagId, ret);
- break;
+ pullMobileBytesTransferByFgBg(tagId, ret);
+ break;
}
case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
- pullBluetoothBytesTransfer(tagId, ret);
- break;
+ pullBluetoothBytesTransfer(tagId, ret);
+ break;
}
case StatsLog.KERNEL_WAKELOCK: {
- pullKernelWakelock(tagId, ret);
- break;
+ pullKernelWakelock(tagId, ret);
+ break;
}
case StatsLog.CPU_TIME_PER_FREQ: {
- pullCpuTimePerFreq(tagId, ret);
- break;
+ pullCpuTimePerFreq(tagId, ret);
+ break;
}
case StatsLog.WIFI_ACTIVITY_ENERGY_INFO: {
- pullWifiActivityEnergyInfo(tagId, ret);
- break;
+ pullWifiActivityEnergyInfo(tagId, ret);
+ break;
}
case StatsLog.MODEM_ACTIVITY_INFO: {
- pullModemActivityInfo(tagId, ret);
- break;
+ pullModemActivityInfo(tagId, ret);
+ break;
}
case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
- pullBluetoothActivityInfo(tagId, ret);
- break;
+ pullBluetoothActivityInfo(tagId, ret);
+ break;
}
case StatsLog.SYSTEM_UPTIME: {
- pullSystemUpTime(tagId, ret);
- break;
+ pullSystemUpTime(tagId, ret);
+ break;
}
case StatsLog.SYSTEM_ELAPSED_REALTIME: {
- pullSystemElapsedRealtime(tagId, ret);
- break;
+ pullSystemElapsedRealtime(tagId, ret);
+ break;
}
case StatsLog.DISK_SPACE: {
- pullDiskSpace(tagId, ret);
- break;
+ pullDiskSpace(tagId, ret);
+ break;
}
default:
Slog.w(TAG, "No such tagId data as " + tagId);
@@ -788,14 +790,14 @@
@Override
public void triggerUidSnapshot() {
- enforceCallingPermission();
- synchronized (sStatsdLock) {
- try {
- informAllUidsLocked(mContext);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+ enforceCallingPermission();
+ synchronized (sStatsdLock) {
+ try {
+ informAllUidsLocked(mContext);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+ }
}
- }
}
private void enforceCallingPermission() {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7c170ae..343fb91 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -321,7 +321,7 @@
public void showChargingAnimation(int batteryLevel) {
if (mBar != null) {
try {
- mBar.showChargingAnimation(batteryLevel);
+ mBar.showWirelessChargingAnimation(batteryLevel);
} catch (RemoteException ex){
}
}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 853c7eb..0ac853b 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -43,7 +42,6 @@
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
/**
* A manager for TextClassifier services.
@@ -54,9 +52,6 @@
private static final String LOG_TAG = "TextClassificationManagerService";
- // How long after the last interaction with the service we would unbind
- private static final long TIMEOUT_IDLE_BIND_MILLIS = TimeUnit.MINUTES.toMillis(1);
-
public static final class Lifecycle extends SystemService {
private final TextClassificationManagerService mManagerService;
@@ -79,10 +74,8 @@
}
private final Context mContext;
- private final Handler mHandler;
private final Intent mServiceIntent;
private final ServiceConnection mConnection;
- private final Runnable mUnbind;
private final Object mLock;
@GuardedBy("mLock")
private final Queue<PendingRequest> mPendingRequests;
@@ -94,7 +87,6 @@
private TextClassificationManagerService(Context context) {
mContext = Preconditions.checkNotNull(context);
- mHandler = new Handler();
mServiceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE)
.setComponent(TextClassifierService.getServiceComponentName(mContext));
mConnection = new ServiceConnection() {
@@ -131,7 +123,6 @@
}
};
mPendingRequests = new LinkedList<>();
- mUnbind = this::unbind;
mLock = new Object();
}
@@ -152,7 +143,6 @@
if (isBoundLocked()) {
mService.onSuggestSelection(
text, selectionStartIndex, selectionEndIndex, options, callback);
- scheduleUnbindLocked();
} else {
final Callable<Void> request = () -> {
onSuggestSelection(
@@ -184,7 +174,6 @@
synchronized (mLock) {
if (isBoundLocked()) {
mService.onClassifyText(text, startIndex, endIndex, options, callback);
- scheduleUnbindLocked();
} else {
final Callable<Void> request = () -> {
onClassifyText(text, startIndex, endIndex, options, callback);
@@ -213,7 +202,6 @@
synchronized (mLock) {
if (isBoundLocked()) {
mService.onGenerateLinks(text, options, callback);
- scheduleUnbindLocked();
} else {
final Callable<Void> request = () -> {
onGenerateLinks(text, options, callback);
@@ -270,27 +258,6 @@
mBinding = binding;
}
- private void unbind() {
- synchronized (mLock) {
- if (!isBoundLocked()) {
- return;
- }
-
- Slog.d(LOG_TAG, "Unbinding from " + mServiceIntent.getComponent());
- mContext.unbindService(mConnection);
-
- synchronized (mLock) {
- mService = null;
- }
- }
- }
-
- @GuardedBy("mLock")
- private void scheduleUnbindLocked() {
- mHandler.removeCallbacks(mUnbind);
- mHandler.postDelayed(mUnbind, TIMEOUT_IDLE_BIND_MILLIS);
- }
-
@GuardedBy("mLock")
private void enqueueRequestLocked(
Callable<Void> request, Callable<Void> onServiceFailure, IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2512dbd..41a6e2b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -725,8 +725,9 @@
* wallpaper windows in the window list.
*/
DisplayContent(Display display, WindowManagerService service,
- WallpaperController wallpaperController) {
+ WallpaperController wallpaperController, DisplayWindowController controller) {
super(service);
+ setController(controller);
if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -1941,6 +1942,8 @@
} finally {
mRemovingDisplay = false;
}
+
+ mService.onDisplayRemoved(mDisplayId);
}
/** Returns true if a removal action is still being deferred. */
@@ -1950,7 +1953,6 @@
if (!stillDeferringRemoval && mDeferredRemoval) {
removeImmediately();
- mService.onDisplayRemoved(mDisplayId);
return false;
}
return true;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index ad4957e..0e12838 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -16,11 +16,14 @@
package com.android.server.wm;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.content.res.Configuration;
+import android.os.Binder;
import android.util.Slog;
+import android.view.Display;
/**
* Controller for the display container. This is created by activity manager to link activity
@@ -36,9 +39,16 @@
mDisplayId = displayId;
synchronized (mWindowMap) {
- // TODO: Convert to setContainer() from DisplayContent once everything is hooked up.
- // Currently we are not setup to register for config changes.
- mContainer = mRoot.getDisplayContentOrCreate(displayId);
+ final Display display = mService.mDisplayManager.getDisplay(displayId);
+ if (display != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mRoot.createDisplayContent(display, this /* controller */);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
if (mContainer == null) {
throw new IllegalArgumentException("Trying to add displayId=" + displayId);
}
@@ -47,14 +57,22 @@
@Override
public void removeContainer() {
- // TODO: Pipe through from ActivityDisplay to remove the display
- throw new UnsupportedOperationException("To be implemented");
+ synchronized (mWindowMap) {
+ if(mContainer == null) {
+ if (DEBUG_DISPLAY) Slog.i(TAG_WM, "removeDisplay: could not find displayId="
+ + mDisplayId);
+ return;
+ }
+ mContainer.removeIfPossible();
+ super.removeContainer();
+ }
}
@Override
public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
- // TODO: Pipe through from ActivityDisplay to update the configuration for the display
- throw new UnsupportedOperationException("To be implemented");
+ // TODO: The container receives override configuration changes through other means. enabling
+ // callbacks through the controller causes layout issues. Investigate consolidating
+ // override configuration propagation to just here.
}
/**
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index fe5b65c..e869f58 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -62,6 +62,7 @@
// The recents component app token that is shown behind the visibile tasks
private AppWindowToken mHomeAppToken;
+ private Rect mMinimizedHomeBounds = new Rect();
// We start the RecentsAnimationController in a pending-start state since we need to wait for
// the wallpaper/activity to draw before we can give control to the handler to start animating
@@ -105,7 +106,7 @@
final AppWindowToken topChild = task.getTopChild();
final WindowState mainWindow = topChild.findMainWindow();
return new TaskSnapshot(buffer, topChild.getConfiguration().orientation,
- mainWindow.mStableInsets,
+ mainWindow.mContentInsets,
ActivityManager.isLowRamDeviceStatic() /* reduced */,
1.0f /* scale */);
}
@@ -163,8 +164,6 @@
* @param remoteAnimationRunner The remote runner which should be notified when the animation is
* ready to start or has been canceled
* @param callbacks Callbacks to be made when the animation finishes
- * @param restoreHomeBehindStackId The stack id to restore the home stack behind once the
- * animation is complete. Will be passed to the callback.
*/
RecentsAnimationController(WindowManagerService service,
IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks,
@@ -200,13 +199,15 @@
if (recentsComponentAppToken != null) {
if (DEBUG) Log.d(TAG, "setHomeApp(" + recentsComponentAppToken.getName() + ")");
mHomeAppToken = recentsComponentAppToken;
- final WallpaperController wc = dc.mWallpaperController;
if (recentsComponentAppToken.windowsCanBeWallpaperTarget()) {
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
dc.setLayoutNeeded();
}
}
+ // Save the minimized home height
+ dc.getDockedDividerController().getHomeStackBoundsInDockedMode(mMinimizedHomeBounds);
+
mService.mWindowPlacerLocked.performSurfacePlacement();
}
@@ -232,7 +233,15 @@
appAnimations[i] = mPendingAnimations.get(i).createRemoteAnimationApp();
}
mPendingStart = false;
- mRunner.onAnimationStart(mController, appAnimations);
+
+ final Rect minimizedHomeBounds =
+ mHomeAppToken != null && mHomeAppToken.inSplitScreenSecondaryWindowingMode()
+ ? mMinimizedHomeBounds : null;
+ final Rect contentInsets =
+ mHomeAppToken != null && mHomeAppToken.findMainWindow() != null
+ ? mHomeAppToken.findMainWindow().mContentInsets : null;
+ mRunner.onAnimationStart_New(mController, appAnimations, contentInsets,
+ minimizedHomeBounds);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to start recents animation", e);
}
@@ -334,11 +343,15 @@
}
RemoteAnimationTarget createRemoteAnimationApp() {
- // TODO: Do we need position and stack bounds?
+ final Point position = new Point();
+ final Rect bounds = new Rect();
+ final WindowContainer container = mTask.getParent();
+ container.getRelativePosition(position);
+ container.getBounds(bounds);
+ final WindowState mainWindow = mTask.getTopVisibleAppMainWindow();
return new RemoteAnimationTarget(mTask.mTaskId, MODE_CLOSING, mCapturedLeash,
- !mTask.fillsParent(),
- mTask.getTopVisibleAppMainWindow().mWinAnimator.mLastClipRect,
- mTask.getPrefixOrderIndex(), new Point(), new Rect(),
+ !mTask.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
+ mainWindow.mContentInsets, mTask.getPrefixOrderIndex(), position, bounds,
mTask.getWindowConfiguration());
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 5bc739e..c353c1d 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -96,17 +96,13 @@
// Scale the timeout with the animator scale the controlling app is using.
mHandler.postDelayed(mTimeoutRunnable,
(long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
-
- final RemoteAnimationTarget[] animations = createAnimations();
- mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
- try {
- mRemoteAnimationAdapter.getRunner().onAnimationStart(animations,
- mFinishedCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to start remote animation", e);
- onAnimationFinished();
- }
- });
+ try {
+ mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(),
+ mFinishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to start remote animation", e);
+ onAnimationFinished();
+ }
}
private RemoteAnimationTarget[] createAnimations() {
@@ -170,7 +166,7 @@
}
return new RemoteAnimationTarget(task.mTaskId, getMode(),
mCapturedLeash, !mAppWindowToken.fillsParent(),
- mainWindow.mWinAnimator.mLastClipRect,
+ mainWindow.mWinAnimator.mLastClipRect, mainWindow.mContentInsets,
mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds,
task.getWindowConfiguration());
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 8d1a822..f5760e5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -193,30 +193,6 @@
}
}
- /**
- * Retrieve the DisplayContent for the specified displayId. Will create a new DisplayContent if
- * there is a Display for the displayId.
- *
- * @param displayId The display the caller is interested in.
- * @return The DisplayContent associated with displayId or null if there is no Display for it.
- */
- DisplayContent getDisplayContentOrCreate(int displayId) {
- DisplayContent dc = getDisplayContent(displayId);
-
- if (dc == null) {
- final Display display = mService.mDisplayManager.getDisplay(displayId);
- if (display != null) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- dc = createDisplayContent(display);
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
- }
- }
- return dc;
- }
-
DisplayContent getDisplayContent(int displayId) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent current = mChildren.get(i);
@@ -227,9 +203,9 @@
return null;
}
- private DisplayContent createDisplayContent(final Display display) {
- final DisplayContent dc = new DisplayContent(display, mService,
- mWallpaperController);
+ DisplayContent createDisplayContent(final Display display, DisplayWindowController controller) {
+ final DisplayContent dc =
+ new DisplayContent(display, mService, mWallpaperController, controller);
final int displayId = display.getDisplayId();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
@@ -612,8 +588,6 @@
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
- mService.mAnimator.executeAfterPrepareSurfacesRunnables();
-
final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
// If we are ready to perform an app transition, check through all of the app tokens to be
@@ -750,19 +724,6 @@
(mSustainedPerformanceModeEnabled ? 1 : 0));
}
- if (mService.mTurnOnScreen) {
- if (mService.mAllowTheaterModeWakeFromLayout
- || Settings.Global.getInt(mService.mContext.getContentResolver(),
- Settings.Global.THEATER_MODE_ON, 0) == 0) {
- if (DEBUG_VISIBILITY || DEBUG_POWER) {
- Slog.v(TAG, "Turning screen on after layout!");
- }
- mService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
- "android.server.wm:TURN_ON");
- }
- mService.mTurnOnScreen = false;
- }
-
if (mUpdateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
// TODO(multi-display): Update rotation for different displays separately.
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index dc62cc8..1b2f954 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -151,6 +151,7 @@
}
}
+ @GuardedBy("mLock")
private void startPendingAnimationsLocked() {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
startAnimationLocked(mPendingAnimations.valueAt(i));
@@ -158,6 +159,7 @@
mPendingAnimations.clear();
}
+ @GuardedBy("mLock")
private void startAnimationLocked(RunningAnimation a) {
final ValueAnimator anim = mAnimatorFactory.makeAnimator();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 7b047a8..621bee7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -281,11 +281,13 @@
mSnapshot = snapshot;
}
+ @GuardedBy("mLock")
@Override
void onQueuedLocked() {
mStoreQueueItems.offer(this);
}
+ @GuardedBy("mLock")
@Override
void onDequeuedLocked() {
mStoreQueueItems.remove(this);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index da3a035..f2ad6fb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -272,8 +272,6 @@
}
boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
- int xOffset = 0;
- int yOffset = 0;
boolean rawChanged = false;
// Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
// match the behavior of most Launchers
@@ -285,8 +283,11 @@
if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
offset += mLastWallpaperDisplayOffsetX;
}
- xOffset = offset;
-
+ boolean changed = wallpaperWin.mXOffset != offset;
+ if (changed) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " x: " + offset);
+ wallpaperWin.mXOffset = offset;
+ }
if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
wallpaperWin.mWallpaperX = wpx;
wallpaperWin.mWallpaperXStep = wpxs;
@@ -300,16 +301,17 @@
if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
offset += mLastWallpaperDisplayOffsetY;
}
- yOffset = offset;
-
+ if (wallpaperWin.mYOffset != offset) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset);
+ changed = true;
+ wallpaperWin.mYOffset = offset;
+ }
if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
wallpaperWin.mWallpaperY = wpy;
wallpaperWin.mWallpaperYStep = wpys;
rawChanged = true;
}
- boolean changed = wallpaperWin.mWinAnimator.setWallpaperOffset(xOffset, yOffset);
-
if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
try {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index ddda027..2ae5c7b 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -74,6 +74,10 @@
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+ final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+ winAnimator.computeShownFrameLocked();
+ // No need to lay out the windows - we can just set the wallpaper position directly.
+ winAnimator.setWallpaperOffset(wallpaper.mShownPosition);
// We only want to be synchronous with one wallpaper.
sync = false;
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b0d42f2..49a30d5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -92,7 +92,6 @@
* executed and the corresponding transaction is closed and applied.
*/
private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
- private boolean mInExecuteAfterPrepareSurfacesRunnables;
WindowAnimator(final WindowManagerService service) {
mService = service;
@@ -162,7 +161,7 @@
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final ScreenRotationAnimation screenRotationAnimation =
@@ -196,7 +195,7 @@
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
dc.checkAppWindowsReadyToShow();
@@ -229,7 +228,7 @@
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
dc.onPendingTransactionApplied();
}
@@ -306,7 +305,7 @@
pw.println(":");
final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final DisplayContent dc =
- mService.mRoot.getDisplayContentOrCreate(mDisplayContentsAnimators.keyAt(i));
+ mService.mRoot.getDisplayContent(mDisplayContentsAnimators.keyAt(i));
dc.dumpWindowAnimators(pw, subPrefix);
if (displayAnimator.mScreenRotationAnimation != null) {
pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
@@ -340,7 +339,7 @@
if (displayId < 0) {
return 0;
}
- final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
return (displayContent != null) ? displayContent.pendingLayoutChanges : 0;
}
@@ -348,7 +347,7 @@
if (displayId < 0) {
return;
}
- final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
if (displayContent != null) {
displayContent.pendingLayoutChanges |= changes;
}
@@ -439,13 +438,7 @@
scheduleAnimation();
}
- void executeAfterPrepareSurfacesRunnables() {
-
- // Don't even think about to start recursing!
- if (mInExecuteAfterPrepareSurfacesRunnables) {
- return;
- }
- mInExecuteAfterPrepareSurfacesRunnables = true;
+ private void executeAfterPrepareSurfacesRunnables() {
// Traverse in order they were added.
final int size = mAfterPrepareSurfacesRunnables.size();
@@ -453,6 +446,5 @@
mAfterPrepareSurfacesRunnables.get(i).run();
}
mAfterPrepareSurfacesRunnables.clear();
- mInExecuteAfterPrepareSurfacesRunnables = false;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 676fb9f..c2ed2ae 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -737,7 +737,6 @@
final InputManagerService mInputManager;
final DisplayManagerInternal mDisplayManagerInternal;
final DisplayManager mDisplayManager;
- private final Display[] mDisplays;
// Indicates whether this device supports wide color gamut rendering
private boolean mHasWideColorGamutSupport;
@@ -746,8 +745,6 @@
private Session mHoldingScreenOn;
private PowerManager.WakeLock mHoldingScreenWakeLock;
- boolean mTurnOnScreen;
-
// Whether or not a layout can cause a wake up when theater mode is enabled.
boolean mAllowTheaterModeWakeFromLayout;
@@ -915,7 +912,6 @@
@Override
public void run() {
WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
-
mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
}
}, 0);
@@ -974,10 +970,6 @@
}
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
- mDisplays = mDisplayManager.getDisplays();
- for (Display display : mDisplays) {
- createDisplayContentLocked(display);
- }
mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
@@ -1066,6 +1058,13 @@
mDragDropController = new DragDropController(this, mH.getLooper());
LocalServices.addService(WindowManagerInternal.class, new LocalService());
+ }
+
+ /**
+ * Called after all entities (such as the {@link ActivityManagerService}) have been set up and
+ * associated with the {@link WindowManagerService}.
+ */
+ public void onInitReady() {
initPolicy();
// Add ourself to the Watchdog monitors.
@@ -1081,6 +1080,7 @@
showEmulatorDisplayOverlayIfNeeded();
}
+
public InputMonitor getInputMonitor() {
return mInputMonitor;
}
@@ -1131,7 +1131,7 @@
throw new IllegalStateException("Display has not been initialialized");
}
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
+ displayId + ". Aborting.");
@@ -2277,7 +2277,7 @@
}
synchronized(mWindowMap) {
- final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
WindowToken token = dc.getWindowToken(binder);
if (token != null) {
Slog.w(TAG_WM, "addWindowToken: Attempted to add binder token: " + binder
@@ -3666,7 +3666,7 @@
boolean wallpaperOnly) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
- displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for "
+ "displayId=" + displayId);
@@ -3885,7 +3885,7 @@
public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
int displayId) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
throw new IllegalArgumentException("Trying to register visibility event "
+ "for invalid display: " + displayId);
@@ -4442,10 +4442,13 @@
}
public void displayReady() {
- for (Display display : mDisplays) {
+ final int displayCount = mRoot.mChildren.size();
+ for (int i = 0; i < displayCount; ++i) {
+ final DisplayContent display = mRoot.mChildren.get(i);
displayReady(display.getDisplayId());
}
+
synchronized(mWindowMap) {
final DisplayContent displayContent = getDefaultDisplayContentLocked();
if (mMaxUiWidth > 0) {
@@ -4476,7 +4479,7 @@
private void displayReady(int displayId) {
synchronized(mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
mAnimator.addDisplayLocked(displayId);
displayContent.initializeDisplayBaseInfo();
@@ -5025,7 +5028,7 @@
@Override
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
size.x = displayContent.mInitialDisplayWidth;
size.y = displayContent.mInitialDisplayHeight;
@@ -5036,7 +5039,7 @@
@Override
public void getBaseDisplaySize(int displayId, Point size) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
size.x = displayContent.mBaseDisplayWidth;
size.y = displayContent.mBaseDisplayHeight;
@@ -5063,7 +5066,7 @@
final int MIN_WIDTH = 200;
final int MIN_HEIGHT = 200;
final int MAX_SCALE = 2;
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
width = Math.min(Math.max(width, MIN_WIDTH),
displayContent.mInitialDisplayWidth * MAX_SCALE);
@@ -5093,7 +5096,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
if (mode < 0 || mode > 1) {
mode = 0;
@@ -5175,7 +5178,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth,
displayContent.mInitialDisplayHeight);
@@ -5191,7 +5194,7 @@
@Override
public int getInitialDisplayDensity(int displayId) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
return displayContent.mInitialDisplayDensity;
}
@@ -5202,7 +5205,7 @@
@Override
public int getBaseDisplayDensity(int displayId) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
return displayContent.mBaseDisplayDensity;
}
@@ -5228,7 +5231,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && mCurrentUserId == targetUserId) {
setForcedDisplayDensityLocked(displayContent, density);
}
@@ -5259,7 +5262,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && mCurrentUserId == callingUserId) {
setForcedDisplayDensityLocked(displayContent,
displayContent.mInitialDisplayDensity);
@@ -5351,7 +5354,7 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
setOverscanLocked(displayContent, left, top, right, bottom);
}
@@ -6614,19 +6617,11 @@
synchronized (mWindowMap) { }
}
- // TODO: All the display method below should probably be moved into the RootWindowContainer...
- private void createDisplayContentLocked(final Display display) {
- if (display == null) {
- throw new IllegalArgumentException("getDisplayContent: display must not be null");
- }
- mRoot.getDisplayContentOrCreate(display.getDisplayId());
- }
-
// There is an inherent assumption that this will never return null.
// TODO(multi-display): Inspect all the call-points of this method to see if they make sense to
// support non-default display.
DisplayContent getDefaultDisplayContentLocked() {
- return mRoot.getDisplayContentOrCreate(DEFAULT_DISPLAY);
+ return mRoot.getDisplayContent(DEFAULT_DISPLAY);
}
public void onDisplayAdded(int displayId) {
@@ -6641,10 +6636,6 @@
public void onDisplayRemoved(int displayId) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
- if (displayContent != null) {
- displayContent.removeIfPossible();
- }
mAnimator.removeDisplayLocked(displayId);
mWindowPlacerLocked.requestTraversal();
}
@@ -6660,7 +6651,7 @@
public void onDisplayChanged(int displayId) {
synchronized (mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
displayContent.updateDisplayInfo();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3bee1e8..21b4361 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.SurfaceControl.Transaction;
@@ -137,6 +138,7 @@
import static com.android.server.wm.proto.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.proto.WindowStateProto.REQUESTED_HEIGHT;
import static com.android.server.wm.proto.WindowStateProto.REQUESTED_WIDTH;
+import static com.android.server.wm.proto.WindowStateProto.SHOWN_POSITION;
import static com.android.server.wm.proto.WindowStateProto.STABLE_INSETS;
import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
@@ -166,6 +168,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
@@ -264,7 +267,7 @@
// This is a non-system overlay window that is currently force hidden.
private boolean mForceHideNonSystemOverlayWindow;
boolean mAppFreezing;
- boolean mHidden; // Used to determine if to show child windows.
+ boolean mHidden = true; // Used to determine if to show child windows.
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
@@ -284,7 +287,6 @@
int mLayer;
boolean mHaveFrame;
boolean mObscured;
- boolean mTurnOnScreen;
int mLayoutSeq = -1;
@@ -296,6 +298,12 @@
private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
/**
+ * Actual position of the surface shown on-screen (may be modified by animation). These are
+ * in the screen's coordinate space (WITH the compatibility scale applied).
+ */
+ final Point mShownPosition = new Point();
+
+ /**
* Insets that determine the actually visible area. These are in the application's
* coordinate space (without compatibility scale applied).
*/
@@ -454,6 +462,10 @@
int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+ // Wallpaper windows: pixels offset based on above variables.
+ int mXOffset;
+ int mYOffset;
+
/**
* This is set after IWindowSession.relayout() has been called at
* least once for the window. It allows us to detect the situation
@@ -624,6 +636,11 @@
private TapExcludeRegionHolder mTapExcludeRegionHolder;
/**
+ * Used for testing because the real PowerManager is final.
+ */
+ private PowerManagerWrapper mPowerManagerWrapper;
+
+ /**
* Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
@@ -652,9 +669,34 @@
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+ interface PowerManagerWrapper {
+ void wakeUp(long time, String reason);
+
+ boolean isInteractive();
+
+ }
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
- WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
- int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
+ WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
+ int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
+ this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId,
+ ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
+ @Override
+ public void wakeUp(long time, String reason) {
+ service.mPowerManager.wakeUp(time, reason);
+ }
+
+ @Override
+ public boolean isInteractive() {
+ return service.mPowerManager.isInteractive();
+ }
+ });
+ }
+
+ WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
+ WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
+ int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
+ PowerManagerWrapper powerManagerWrapper) {
super(service);
mSession = s;
mClient = c;
@@ -671,6 +713,7 @@
DeathRecipient deathRecipient = new DeathRecipient();
mSeq = seq;
mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
+ mPowerManagerWrapper = powerManagerWrapper;
if (localLOGV) Slog.v(
TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
@@ -734,6 +777,8 @@
mRequestedHeight = 0;
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
+ mXOffset = 0;
+ mYOffset = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
@@ -1099,6 +1144,11 @@
}
@Override
+ public Point getShownPositionLw() {
+ return mShownPosition;
+ }
+
+ @Override
public Rect getDisplayFrameLw() {
return mDisplayFrame;
}
@@ -2257,9 +2307,34 @@
void prepareWindowToDisplayDuringRelayout(boolean wasVisible) {
// We need to turn on screen regardless of visibility.
- if ((mAttrs.flags & FLAG_TURN_SCREEN_ON) != 0) {
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Relayout window turning screen on: " + this);
- mTurnOnScreen = true;
+ boolean hasTurnScreenOnFlag = (mAttrs.flags & FLAG_TURN_SCREEN_ON) != 0;
+ boolean allowTheaterMode =
+ mService.mAllowTheaterModeWakeFromLayout || Settings.Global.getInt(
+ mService.mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0)
+ == 0;
+ boolean canTurnScreenOn = mAppToken == null || mAppToken.canTurnScreenOn();
+
+ // The screen will turn on if the following conditions are met
+ // 1. The window has the flag FLAG_TURN_SCREEN_ON
+ // 2. The WMS allows theater mode.
+ // 3. No AWT or the AWT allows the screen to be turned on. This should only be true once
+ // per resume to prevent the screen getting getting turned on for each relayout. Set
+ // canTurnScreenOn will be set to false so the window doesn't turn the screen on again
+ // during this resume.
+ // 4. When the screen is not interactive. This is because when the screen is already
+ // interactive, the value may persist until the next animation, which could potentially
+ // be occurring while turning off the screen. This would lead to the screen incorrectly
+ // turning back on.
+ if (hasTurnScreenOnFlag && allowTheaterMode && canTurnScreenOn
+ && !mPowerManagerWrapper.isInteractive()) {
+ if (DEBUG_VISIBILITY || DEBUG_POWER) {
+ Slog.v(TAG, "Relayout window turning screen on: " + this);
+ }
+ mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(),
+ "android.server.wm:TURN_ON");
+ }
+ if (mAppToken != null) {
+ mAppToken.setCanTurnScreenOn(false);
}
// If we were already visible, skip rest of preparation.
@@ -2553,8 +2628,7 @@
// in wake lock statistics. So in particular, we don't want to include the
// window's hash code as in toString().
final CharSequence tag = getWindowTag();
- mDrawLock = mService.mPowerManager.newWakeLock(
- PowerManager.DRAW_WAKE_LOCK, "Window:" + tag);
+ mDrawLock = mService.mPowerManager.newWakeLock(DRAW_WAKE_LOCK, "Window:" + tag);
mDrawLock.setReferenceCounted(false);
mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName));
}
@@ -3116,6 +3190,7 @@
mContentInsets.writeToProto(proto, CONTENT_INSETS);
mAttrs.surfaceInsets.writeToProto(proto, SURFACE_INSETS);
mSurfacePosition.writeToProto(proto, SURFACE_POSITION);
+ mShownPosition.writeToProto(proto, SHOWN_POSITION);
mWinAnimator.writeToProto(proto, ANIMATOR);
proto.write(ANIMATING_EXIT, mAnimatingExit);
for (int i = 0; i < mChildren.size(); i++) {
@@ -3231,6 +3306,10 @@
pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);
pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded);
}
+ if (mXOffset != 0 || mYOffset != 0) {
+ pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset);
+ pw.print(" y="); pw.println(mYOffset);
+ }
if (dumpAll) {
pw.print(prefix); pw.print("mGivenContentInsets=");
mGivenContentInsets.printShortString(pw);
@@ -3249,6 +3328,7 @@
pw.println(getLastReportedConfiguration());
}
pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
+ pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay());
pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed);
if (dumpAll) {
@@ -3303,15 +3383,13 @@
pw.print(" mDestroying="); pw.print(mDestroying);
pw.print(" mRemoved="); pw.println(mRemoved);
}
- if (getOrientationChanging() || mAppFreezing || mTurnOnScreen
- || mReportOrientationChanged) {
+ if (getOrientationChanging() || mAppFreezing || mReportOrientationChanged) {
pw.print(prefix); pw.print("mOrientationChanging=");
pw.print(mOrientationChanging);
pw.print(" configOrientationChanging=");
pw.print(getLastReportedConfiguration().orientation
!= getConfiguration().orientation);
pw.print(" mAppFreezing="); pw.print(mAppFreezing);
- pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
pw.print(" mReportOrientationChanged="); pw.println(mReportOrientationChanged);
}
if (mLastFreezeDuration != 0) {
@@ -4171,8 +4249,9 @@
final int width = mFrame.width();
final int height = mFrame.height();
- final int left = mFrame.left;
- final int top = mFrame.top;
+ // Compute the offset of the window in relation to the decor rect.
+ final int left = mXOffset + mFrame.left;
+ final int top = mYOffset + mFrame.top;
// Initialize the decor rect to the entire frame.
if (isDockedResizing()) {
@@ -4367,8 +4446,8 @@
float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx;
float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy;
float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy;
- int x = mSurfacePosition.x;
- int y = mSurfacePosition.y;
+ int x = mSurfacePosition.x + mShownPosition.x;
+ int y = mSurfacePosition.y + mShownPosition.y;
// If changed, also adjust transformFrameToSurfacePosition
final WindowContainer parent = getParent();
@@ -4480,8 +4559,12 @@
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
dimmer.dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
- } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
- && !mAnimatingExit && isVisible() && !mWinAnimator.mLastHidden) {
+ } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && isVisibleNow() && !mHidden) {
+ // Only show a dim behind when the following is satisfied:
+ // 1. The window has the flag FLAG_DIM_BEHIND
+ // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
+ // 3. The WS is considered visible according to the isVisible() method
+ // 4. The WS is not hidden.
mIsDimming = true;
dimmer.dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 499322c..9621ee5 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -209,14 +209,10 @@
float mExtraHScale = (float) 1.0;
float mExtraVScale = (float) 1.0;
- // An offset in pixel of the surface contents from the window position. Used for Wallpaper
- // to provide the effect of scrolling within a large surface. We just use these values as
- // a cache.
- int mXOffset = 0;
- int mYOffset = 0;
-
private final Rect mTmpSize = new Rect();
+ private final SurfaceControl.Transaction mReparentTransaction = new SurfaceControl.Transaction();
+
WindowStateAnimator(final WindowState win) {
final WindowManagerService service = win.mService;
@@ -377,9 +373,9 @@
// child layers need to be reparented to the new surface to make this
// transparent to the app.
if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) {
- SurfaceControl.openTransaction();
- mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController);
- SurfaceControl.closeTransaction();
+ mReparentTransaction.reparentChildren(mPendingDestroySurface.mSurfaceControl,
+ mSurfaceController.mSurfaceControl.getHandle())
+ .apply();
}
}
}
@@ -442,7 +438,7 @@
flags |= SurfaceControl.SECURE;
}
- mTmpSize.set(0, 0, 0, 0);
+ mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0);
calculateSurfaceBounds(w, attrs);
final int width = mTmpSize.width();
final int height = mTmpSize.height();
@@ -593,7 +589,7 @@
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
WindowManagerService.logSurface(mWin, "DESTROY PENDING", true);
}
- mPendingDestroySurface.destroyInTransaction();
+ mPendingDestroySurface.destroyNotInTransaction();
}
mPendingDestroySurface = mSurfaceController;
}
@@ -630,7 +626,7 @@
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
WindowManagerService.logSurface(mWin, "DESTROY PENDING", true);
}
- mPendingDestroySurface.destroyInTransaction();
+ mPendingDestroySurface.destroyNotInTransaction();
// Don't hide wallpaper if we're destroying a deferred surface
// after a surface mode change.
if (!mDestroyPreservedSurfaceUponRedraw) {
@@ -683,8 +679,8 @@
// WindowState.prepareSurfaces expands for surface insets (in order they don't get
// clipped by the WindowState surface), so we need to go into the other direction here.
- tmpMatrix.postTranslate(mWin.mAttrs.surfaceInsets.left,
- mWin.mAttrs.surfaceInsets.top);
+ tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
+ mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
// "convert" it into SurfaceFlinger's format
@@ -699,6 +695,9 @@
mDtDx = tmpFloats[Matrix.MSKEW_Y];
mDtDy = tmpFloats[Matrix.MSKEW_X];
mDsDy = tmpFloats[Matrix.MSCALE_Y];
+ float x = tmpFloats[Matrix.MTRANS_X];
+ float y = tmpFloats[Matrix.MTRANS_Y];
+ mWin.mShownPosition.set(Math.round(x), Math.round(y));
// Now set the alpha... but because our current hardware
// can't do alpha transformation on a non-opaque surface,
@@ -708,7 +707,8 @@
mShownAlpha = mAlpha;
if (!mService.mLimitedAlphaCompositing
|| (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
- || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)))) {
+ || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)
+ && x == frame.left && y == frame.top))) {
//Slog.i(TAG_WM, "Applying alpha transform");
if (screenAnimation) {
mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
@@ -738,6 +738,10 @@
TAG, "computeShownFrameLocked: " + this +
" not attached, mAlpha=" + mAlpha);
+ // WindowState.prepareSurfaces expands for surface insets (in order they don't get
+ // clipped by the WindowState surface), so we need to go into the other direction here.
+ mWin.mShownPosition.set(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
+ mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
mShownAlpha = mAlpha;
mHaveMatrix = false;
mDsDx = mWin.mGlobalScale;
@@ -788,6 +792,12 @@
if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect
+ " fullscreen=" + fullscreen);
+ if (isFreeformResizing && !w.isChildWindow()) {
+ // For freeform resizing non child windows, we are using the big surface positioned
+ // at 0,0. Thus we must express the crop in that coordinate space.
+ clipRect.offset(w.mShownPosition.x, w.mShownPosition.y);
+ }
+
w.expandForSurfaceInsets(clipRect);
// The clip rect was generated assuming (0,0) as the window origin,
@@ -829,7 +839,7 @@
return;
}
- mTmpSize.set(0, 0, 0, 0);
+ mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
calculateSurfaceBounds(w, attrs);
mExtraHScale = (float) 1.0;
@@ -963,6 +973,11 @@
// then take over the scaling until the new buffer arrives, and things
// will be seamless.
mForceScaleUntilResize = true;
+ } else {
+ if (!w.mSeamlesslyRotated) {
+ mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top,
+ recoveringMemory);
+ }
}
// If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE
@@ -1093,31 +1108,6 @@
w.setOrientationChanging(false);
}
}
- // We process mTurnOnScreen even for windows which have already
- // been shown, to handle cases where windows are not necessarily
- // hidden while the screen is turning off.
- // TODO(b/63773439): These cases should be eliminated, though we probably still
- // want to process mTurnOnScreen in this way for clarity.
- if (mWin.mTurnOnScreen &&
- (mWin.mAppToken == null || mWin.mAppToken.canTurnScreenOn())) {
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Show surface turning screen on: " + mWin);
- mWin.mTurnOnScreen = false;
-
- // The window should only turn the screen on once per resume, but
- // prepareSurfaceLocked can be called multiple times. Set canTurnScreenOn to
- // false so the window doesn't turn the screen on again during this resume.
- if (mWin.mAppToken != null) {
- mWin.mAppToken.setCanTurnScreenOn(false);
- }
-
- // We do not add {@code SET_TURN_ON_SCREEN} when the screen is already
- // interactive as the value may persist until the next animation, which could
- // potentially occurring while turning off the screen. This would lead to the
- // screen incorrectly turning back on.
- if (!mService.mPowerManager.isInteractive()) {
- mService.mTurnOnScreen = true;
- }
- }
}
if (hasSurface()) {
w.mToken.hasVisible = true;
@@ -1154,26 +1144,24 @@
mSurfaceController.setTransparentRegionHint(region);
}
- boolean setWallpaperOffset(int dx, int dy) {
- if (mXOffset == dx && mYOffset == dy) {
- return false;
- }
- mXOffset = dx;
- mYOffset = dy;
+ void setWallpaperOffset(Point shownPosition) {
+ final LayoutParams attrs = mWin.getAttrs();
+ final int left = shownPosition.x - attrs.surfaceInsets.left;
+ final int top = shownPosition.y - attrs.surfaceInsets.top;
try {
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
mService.openSurfaceTransaction();
- mSurfaceController.setPositionInTransaction(dx, dy, false);
+ mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
+ mWin.mFrame.top + top, false);
applyCrop(null, false);
} catch (RuntimeException e) {
Slog.w(TAG, "Error positioning surface of " + mWin
- + " pos=(" + dx + "," + dy + ")", e);
+ + " pos=(" + left + "," + top + ")", e);
} finally {
mService.closeSurfaceTransaction("setWallpaperOffset");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setWallpaperOffset");
- return true;
}
}
@@ -1408,7 +1396,7 @@
void destroySurface() {
try {
if (mSurfaceController != null) {
- mSurfaceController.destroyInTransaction();
+ mSurfaceController.destroyNotInTransaction();
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying surface " + this
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 2f38556..554a600 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -168,7 +168,7 @@
}
}
- void destroyInTransaction() {
+ void destroyNotInTransaction() {
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(8));
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7540e26..4045b72 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -24,6 +24,7 @@
"com_android_server_connectivity_Vpn.cpp",
"com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
"com_android_server_ConsumerIrService.cpp",
+ "com_android_server_devicepolicy_CryptoTestHelper.cpp",
"com_android_server_HardwarePropertiesManagerService.cpp",
"com_android_server_hdmi_HdmiCecController.cpp",
"com_android_server_input_InputApplicationHandle.cpp",
@@ -123,6 +124,7 @@
"android.hardware.tv.input@1.0",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
+ "android.hardware.vibrator@1.2",
"android.hardware.vr@1.0",
"android.frameworks.schedulerservice@1.0",
"android.frameworks.sensorservice@1.0",
diff --git a/services/core/jni/com_android_server_ArcVideoService.cpp b/services/core/jni/com_android_server_ArcVideoService.cpp
index 7df8276..f93cd90 100644
--- a/services/core/jni/com_android_server_ArcVideoService.cpp
+++ b/services/core/jni/com_android_server_ArcVideoService.cpp
@@ -32,7 +32,7 @@
#include <arc/Future.h>
#include <arc/IArcBridgeService.h>
#include <arc/MojoProcessSupport.h>
-#include <video.mojom.h>
+#include <components/arc/common/video.mojom.h>
namespace {
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index d2f374d..016de14 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -18,7 +18,10 @@
#include <android/hardware/vibrator/1.0/IVibrator.h>
#include <android/hardware/vibrator/1.0/types.h>
-#include <android/hardware/vibrator/1.1/IVibrator.h>
+#include <android/hardware/vibrator/1.0/IVibrator.h>
+#include <android/hardware/vibrator/1.1/types.h>
+#include <android/hardware/vibrator/1.2/IVibrator.h>
+#include <android/hardware/vibrator/1.2/types.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
@@ -32,15 +35,15 @@
#include <stdio.h>
using android::hardware::Return;
-using android::hardware::vibrator::V1_0::Effect;
using android::hardware::vibrator::V1_0::EffectStrength;
-using android::hardware::vibrator::V1_0::IVibrator;
using android::hardware::vibrator::V1_0::Status;
using android::hardware::vibrator::V1_1::Effect_1_1;
-using IVibrator_1_1 = android::hardware::vibrator::V1_1::IVibrator;
-namespace android
-{
+namespace V1_0 = android::hardware::vibrator::V1_0;
+namespace V1_1 = android::hardware::vibrator::V1_1;
+namespace V1_2 = android::hardware::vibrator::V1_2;
+
+namespace android {
static constexpr int NUM_TRIES = 2;
@@ -84,19 +87,29 @@
return ret;
}
+template<class R>
+bool isValidEffect(jlong effect) {
+ if (effect < 0) {
+ return false;
+ }
+ R val = static_cast<R>(effect);
+ auto iter = hardware::hidl_enum_iterator<R>();
+ return val >= *iter.begin() && val < *std::prev(iter.end());
+}
+
static void vibratorInit(JNIEnv /* env */, jobject /* clazz */)
{
- halCall(&IVibrator::ping).isOk();
+ halCall(&V1_0::IVibrator::ping).isOk();
}
static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
{
- return halCall(&IVibrator::ping).isOk() ? JNI_TRUE : JNI_FALSE;
+ return halCall(&V1_0::IVibrator::ping).isOk() ? JNI_TRUE : JNI_FALSE;
}
static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
- Status retStatus = halCall(&IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
+ Status retStatus = halCall(&V1_0::IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
if (retStatus != Status::OK) {
ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
@@ -104,18 +117,18 @@
static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
- Status retStatus = halCall(&IVibrator::off).withDefault(Status::UNKNOWN_ERROR);
+ Status retStatus = halCall(&V1_0::IVibrator::off).withDefault(Status::UNKNOWN_ERROR);
if (retStatus != Status::OK) {
ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
}
static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
- return halCall(&IVibrator::supportsAmplitudeControl).withDefault(false);
+ return halCall(&V1_0::IVibrator::supportsAmplitudeControl).withDefault(false);
}
static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
- Status status = halCall(&IVibrator::setAmplitude, static_cast<uint32_t>(amplitude))
+ Status status = halCall(&V1_0::IVibrator::setAmplitude, static_cast<uint32_t>(amplitude))
.withDefault(Status::UNKNOWN_ERROR);
if (status != Status::OK) {
ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
@@ -132,22 +145,25 @@
};
EffectStrength effectStrength(static_cast<EffectStrength>(strength));
- if (effect < 0 || effect > static_cast<uint32_t>(Effect_1_1::TICK)) {
+ Return<void> ret;
+ if (isValidEffect<V1_0::Effect>(effect)) {
+ ret = halCall(&V1_0::IVibrator::perform, static_cast<V1_0::Effect>(effect),
+ effectStrength, callback);
+ } else if (isValidEffect<Effect_1_1>(effect)) {
+ ret = halCall(&V1_1::IVibrator::perform_1_1, static_cast<Effect_1_1>(effect),
+ effectStrength, callback);
+ } else if (isValidEffect<V1_2::Effect>(effect)) {
+ ret = halCall(&V1_2::IVibrator::perform_1_2, static_cast<V1_2::Effect>(effect),
+ effectStrength, callback);
+ } else {
ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
static_cast<int32_t>(effect));
- } else if (effect == static_cast<uint32_t>(Effect_1_1::TICK)) {
- auto ret = halCall(&IVibrator_1_1::perform_1_1, static_cast<Effect_1_1>(effect),
- effectStrength, callback);
- if (!ret.isOk()) {
- ALOGW("Failed to perform effect (%" PRId32 "), insufficient HAL version",
- static_cast<int32_t>(effect));
- }
- } else {
- auto ret = halCall(&IVibrator::perform, static_cast<Effect>(effect), effectStrength,
- callback);
- if (!ret.isOk()) {
- ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect));
- }
+ return -1;
+ }
+
+ if (!ret.isOk()) {
+ ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect));
+ return -1;
}
if (status == Status::OK) {
@@ -160,6 +176,7 @@
", error=%" PRIu32 ").", static_cast<int64_t>(effect),
static_cast<int32_t>(strength), static_cast<uint32_t>(status));
}
+
return -1;
}
diff --git a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
new file mode 100644
index 0000000..b53ea92
--- /dev/null
+++ b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+#include "core_jni_helpers.h"
+
+#include <openssl/crypto.h>
+
+namespace {
+
+static jint runSelfTest(JNIEnv* env, jobject /* clazz */) {
+ return BORINGSSL_self_test();
+}
+
+static const JNINativeMethod methods[] = {
+ /* name, signature, funcPtr */
+ {"runSelfTest", "()I", (void*) runSelfTest}
+};
+
+} // anonymous namespace
+
+namespace android {
+
+int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv *env) {
+ return jniRegisterNativeMethods(
+ env, "com/android/server/devicepolicy/CryptoTestHelper", methods, NELEM(methods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 07ddb05..bf2a637 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -42,6 +42,7 @@
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
+int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -88,6 +89,7 @@
register_android_server_location_GnssLocationProvider(env);
register_android_server_connectivity_Vpn(env);
register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
+ register_android_server_devicepolicy_CryptoTestHelper(env);
register_android_server_ConsumerIrService(env);
register_android_server_BatteryStatsService(env);
register_android_server_hdmi_HdmiCecController(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index c4e485c..3557dc9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -122,16 +122,6 @@
}
@Override
- public void setPrintingEnabled(ComponentName admin, boolean enabled) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isPrintingEnabled() {
- return true;
- }
-
- @Override
public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) {
return packageNames;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java
new file mode 100644
index 0000000..a20758e
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.admin.SecurityLog;
+
+/**
+ * Helper to call native BoringSSL self test.
+ */
+public class CryptoTestHelper {
+ public static void runAndLogSelfTest() {
+ final int result = runSelfTest();
+ SecurityLog.writeEvent(SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED, result);
+ }
+ private static native int runSelfTest();
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
index 60f204d..0c0ce8d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
@@ -193,6 +193,7 @@
}
}
+ @GuardedBy("mLock")
private void disconnectServiceOnUserLocked(int userId, @NonNull String actionForLog) {
final DevicePolicyServiceConnection conn = mConnections.get(userId);
if (conn != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4afe1c7..8753344 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -305,8 +305,6 @@
private static final String TAG_PASSWORD_VALIDITY = "password-validity";
- private static final String TAG_PRINTING_ENABLED = "printing-enabled";
-
private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
@@ -615,8 +613,6 @@
long mPasswordTokenHandle = 0;
- boolean mPrintingEnabled = true;
-
public DevicePolicyData(int userHandle) {
mUserHandle = userHandle;
}
@@ -2048,6 +2044,10 @@
public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
return new TransferOwnershipMetadataManager();
}
+
+ public void runCryptoSelfTest() {
+ CryptoTestHelper.runAndLogSelfTest();
+ }
}
/**
@@ -2300,6 +2300,7 @@
if (hasDeviceOwner && mInjector.securityLogGetLoggingEnabledProperty()) {
mSecurityLogMonitor.start();
+ mInjector.runCryptoSelfTest();
maybePauseDeviceWideLoggingLocked();
}
}
@@ -2948,12 +2949,6 @@
out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
}
- if (!policy.mPrintingEnabled) {
- out.startTag(null, TAG_PRINTING_ENABLED);
- out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mPrintingEnabled));
- out.endTag(null, TAG_PRINTING_ENABLED);
- }
-
for (final String cert : policy.mOwnerInstalledCaCerts) {
out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
out.attribute(null, ATTR_ALIAS, cert);
@@ -3172,9 +3167,6 @@
policy.mCurrentInputMethodSet = true;
} else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
- } else if (TAG_PRINTING_ENABLED.equals(tag)) {
- String enabled = parser.getAttributeValue(null, ATTR_VALUE);
- policy.mPrintingEnabled = Boolean.toString(true).equals(enabled);
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -10234,6 +10226,7 @@
mInjector.registerContentObserver(mDefaultImeChanged, false, this, UserHandle.USER_ALL);
}
+ @GuardedBy("DevicePolicyManagerService.this")
private void addPendingChangeByOwnerLocked(int userId) {
mUserIdsWithPendingChangesByOwner.add(userId);
}
@@ -10417,7 +10410,7 @@
public CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId) {
synchronized (DevicePolicyManagerService.this) {
DevicePolicyData policy = getUserData(userId);
- if (policy.mPrintingEnabled) {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING)) {
Log.e(LOG_TAG, "printing is enabled");
return null;
}
@@ -12789,27 +12782,6 @@
}
}
- private boolean hasPrinting() {
- return mInjector.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
- }
-
- @Override
- public void setPrintingEnabled(ComponentName admin, boolean enabled) {
- if (!mHasFeature || !hasPrinting()) {
- return;
- }
- Preconditions.checkNotNull(admin, "Admin cannot be null.");
- enforceProfileOrDeviceOwner(admin);
- synchronized (this) {
- final int userHandle = mInjector.userHandleGetCallingUserId();
- DevicePolicyData policy = getUserData(userHandle);
- if (policy.mPrintingEnabled != enabled) {
- policy.mPrintingEnabled = enabled;
- saveSettingsLocked(userHandle);
- }
- }
- }
-
private void deleteTransferOwnershipMetadataFileLocked() {
mTransferOwnershipMetadataManager.deleteMetadataFile();
}
@@ -12839,25 +12811,6 @@
}
}
- /**
- * Returns whether printing is enabled for current user.
- * @hide
- */
- @Override
- public boolean isPrintingEnabled() {
- if (!hasPrinting()) {
- return false;
- }
- if (!mHasFeature) {
- return true;
- }
- synchronized (this) {
- final int userHandle = mInjector.userHandleGetCallingUserId();
- DevicePolicyData policy = getUserData(userHandle);
- return policy.mPrintingEnabled;
- }
- }
-
@Override
public int addOverrideApn(@NonNull ComponentName who, @NonNull ApnSetting apnSetting) {
if (!mHasFeature) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3210f1a..75bb5e4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -65,7 +65,6 @@
import com.android.server.audio.AudioService;
import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
-import com.android.server.car.CarServiceHelperService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.coverage.CoverageService;
@@ -220,6 +219,8 @@
"com.google.android.things.services.IoTSystemService";
private static final String SLICE_MANAGER_SERVICE_CLASS =
"com.android.server.slice.SliceManagerService$Lifecycle";
+ private static final String CAR_SERVICE_HELPER_SERVICE_CLASS =
+ "com.android.internal.car.CarServiceHelperService";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -723,7 +724,6 @@
MmsServiceBroker mmsService = null;
HardwarePropertiesManagerService hardwarePropertiesService = null;
- boolean disableRtt = SystemProperties.getBoolean("config.disable_rtt", false);
boolean disableSystemTextClassifier = SystemProperties.getBoolean(
"config.disable_systemtextclassifier", false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -842,6 +842,14 @@
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
traceEnd();
+ traceBeginAndSlog("SetWindowManagerService");
+ mActivityManagerService.setWindowManager(wm);
+ traceEnd();
+
+ traceBeginAndSlog("WindowManagerServiceOnInitReady");
+ wm.onInitReady();
+ traceEnd();
+
// Start receiving calls from HIDL services. Start in in a separate thread
// because it need to connect to SensorManager. This have to start
// after START_SENSOR_SERVICE is done.
@@ -859,10 +867,6 @@
traceEnd();
}
- traceBeginAndSlog("SetWindowManagerService");
- mActivityManagerService.setWindowManager(wm);
- traceEnd();
-
traceBeginAndSlog("StartInputManager");
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
@@ -1104,18 +1108,12 @@
traceEnd();
}
- if (!disableRtt) {
- traceBeginAndSlog("StartWifiRtt");
- mSystemServiceManager.startService("com.android.server.wifi.RttService");
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_RTT)) {
+ traceBeginAndSlog("StartRttService");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.rtt.RttService");
traceEnd();
-
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_RTT)) {
- traceBeginAndSlog("StartRttService");
- mSystemServiceManager.startService(
- "com.android.server.wifi.rtt.RttService");
- traceEnd();
- }
}
if (context.getPackageManager().hasSystemFeature(
@@ -1750,7 +1748,7 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
traceBeginAndSlog("StartCarServiceHelperService");
- mSystemServiceManager.startService(CarServiceHelperService.class);
+ mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
traceEnd();
}
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 7d9736e..d190432 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -1068,6 +1068,7 @@
mLastInstallEvent.flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter);
}
+ @GuardedBy("this")
private void logApfProgramEventLocked(long now) {
if (mLastInstallEvent == null) {
return;
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index 49a1e79..8fbc01e 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -268,6 +268,7 @@
mUnicastResponder = null;
}
+ @GuardedBy("mLock")
private void assembleRaLocked() {
final ByteBuffer ra = ByteBuffer.wrap(mRA);
ra.order(ByteOrder.BIG_ENDIAN);
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index e1c1eb2..e8620ed 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
-import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -115,12 +114,9 @@
private final SparseArray<UserState> mUserStates = new SparseArray<>();
- private final DevicePolicyManager mDpm;
-
PrintManagerImpl(Context context) {
mContext = context;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
registerContentObservers();
registerBroadcastReceivers();
}
@@ -722,7 +718,7 @@
}
private boolean isPrintingEnabled() {
- return mDpm == null || mDpm.isPrintingEnabled();
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING);
}
private void dump(@NonNull DualDumpOutputStream dumpStream,
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index ba5dde0..c1c32c2 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -596,6 +596,7 @@
}
}
+ @GuardedBy("mLock")
private void bindLocked() throws TimeoutException, InterruptedException {
while (mIsBinding) {
mLock.wait();
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
index e103464..cd9311f 100644
--- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -26,6 +26,8 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -40,7 +42,9 @@
import android.app.IActivityManager;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -53,6 +57,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -80,6 +85,8 @@
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@@ -88,6 +95,7 @@
import org.robolectric.shadows.ShadowQueuedWork;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@@ -184,32 +192,33 @@
@Test
public void testRunTask_whenTransportProvidesFlags_passesThemToTheAgent() throws Exception {
- BackupAgent agent = setUpAgent(PACKAGE_1);
+ AgentMock agentMock = setUpAgent(PACKAGE_1);
int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
when(mTransportBinder.getTransportFlags()).thenReturn(flags);
PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
runTask(task);
- verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ verify(agentMock.agent)
+ .onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
}
@Test
public void testRunTask_whenTransportDoesNotProvidesFlags() throws Exception {
- BackupAgent agent = setUpAgent(PACKAGE_1);
+ AgentMock agentMock = setUpAgent(PACKAGE_1);
PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
runTask(task);
- verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(0)), any());
+ verify(agentMock.agent).onBackup(any(), argThat(dataOutputWithTransportFlags(0)), any());
}
@Test
public void testRunTask_whenTransportProvidesFlagsAndMultipleAgents_passesToAll()
throws Exception {
- List<BackupAgent> agents = setUpAgents(PACKAGE_1, PACKAGE_2);
- BackupAgent agent1 = agents.get(0);
- BackupAgent agent2 = agents.get(1);
+ List<AgentMock> agentMocks = setUpAgents(PACKAGE_1, PACKAGE_2);
+ BackupAgent agent1 = agentMocks.get(0).agent;
+ BackupAgent agent2 = agentMocks.get(1).agent;
int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
when(mTransportBinder.getTransportFlags()).thenReturn(flags);
PerformBackupTask task =
@@ -223,14 +232,103 @@
@Test
public void testRunTask_whenTransportChangeFlagsAfterTaskCreation() throws Exception {
- BackupAgent agent = setUpAgent(PACKAGE_1);
+ AgentMock agentMock = setUpAgent(PACKAGE_1);
PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
when(mTransportBinder.getTransportFlags()).thenReturn(flags);
runTask(task);
- verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ verify(agentMock.agent)
+ .onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ @Test
+ public void testRunTask_callsListenerOnTaskFinished() throws Exception {
+ setUpAgent(PACKAGE_1);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+ runTask(task);
+
+ verify(mListener).onFinished(any());
+ }
+
+ @Test
+ public void testRunTask_callsTransportPerformBackup() throws Exception {
+ AgentMock agentMock = setUpAgent(PACKAGE_1);
+ agentOnBackupDo(
+ agentMock.agent,
+ (oldState, dataOutput, newState) -> {
+ writeData(dataOutput, "key1", "foo".getBytes());
+ writeData(dataOutput, "key2", "bar".getBytes());
+ });
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+ // We need to verify at call time because the file is deleted right after
+ when(mTransportBinder.performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
+ .then(this::mockAndVerifyTransportPerformBackupData);
+
+ runTask(task);
+
+ // Already verified data in mockAndVerifyPerformBackupData
+ verify(mTransportBinder).performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
+ }
+
+ private int mockAndVerifyTransportPerformBackupData(InvocationOnMock invocation)
+ throws IOException {
+ ParcelFileDescriptor data = invocation.getArgument(1);
+
+ // Verifying that what we passed to the transport is what the agent wrote
+ BackupDataInput dataInput = new BackupDataInput(data.getFileDescriptor());
+
+ // "key1" => "foo"
+ assertThat(dataInput.readNextHeader()).isTrue();
+ assertThat(dataInput.getKey()).isEqualTo("key1");
+ int size1 = dataInput.getDataSize();
+ byte[] data1 = new byte[size1];
+ dataInput.readEntityData(data1, 0, size1);
+ assertThat(data1).isEqualTo("foo".getBytes());
+
+ // "key2" => "bar"
+ assertThat(dataInput.readNextHeader()).isTrue();
+ assertThat(dataInput.getKey()).isEqualTo("key2");
+ int size2 = dataInput.getDataSize();
+ byte[] data2 = new byte[size2];
+ dataInput.readEntityData(data2, 0, size2);
+ assertThat(data2).isEqualTo("bar".getBytes());
+
+ // No more
+ assertThat(dataInput.readNextHeader()).isFalse();
+
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ @Test
+ public void testRunTask_whenPerformBackupSucceeds_callsTransportFinishBackup()
+ throws Exception {
+ setUpAgent(PACKAGE_1);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+ when(mTransportBinder.performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
+ .thenReturn(BackupTransport.TRANSPORT_OK);
+
+ runTask(task);
+
+ verify(mTransportBinder).finishBackup();
+ }
+
+ @Test
+ public void testRunTask_whenProhibitedKey_failsAgent() throws Exception {
+ AgentMock agentMock = setUpAgent(PACKAGE_1);
+ agentOnBackupDo(
+ agentMock.agent,
+ (oldState, dataOutput, newState) -> {
+ char prohibitedChar = 0xff00;
+ writeData(dataOutput, prohibitedChar + "key", "foo".getBytes());
+ });
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+ runTask(task);
+
+ verify(agentMock.agentBinder).fail(any());
}
private void runTask(PerformBackupTask task) {
@@ -241,26 +339,34 @@
}
}
- private List<BackupAgent> setUpAgents(String... packageNames) {
+ private List<AgentMock> setUpAgents(String... packageNames) {
return Stream.of(packageNames).map(this::setUpAgent).collect(toList());
}
- private BackupAgent setUpAgent(String packageName) {
- PackageInfo packageInfo = new PackageInfo();
- packageInfo.packageName = packageName;
- packageInfo.applicationInfo = new ApplicationInfo();
- packageInfo.applicationInfo.flags = ApplicationInfo.FLAG_ALLOW_BACKUP;
- packageInfo.applicationInfo.backupAgentName = "BackupAgent" + packageName;
- packageInfo.applicationInfo.packageName = packageName;
- mShadowPackageManager.setApplicationEnabledSetting(
- packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
- mShadowPackageManager.addPackage(packageInfo);
- BackupAgent backupAgent = spy(BackupAgent.class);
- IBackupAgent backupAgentBinder = IBackupAgent.Stub.asInterface(backupAgent.onBind());
- when(mBackupManagerService.bindToAgentSynchronous(
- eq(packageInfo.applicationInfo), anyInt()))
- .thenReturn(backupAgentBinder);
- return backupAgent;
+ private AgentMock setUpAgent(String packageName) {
+ try {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags = ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.backupAgentName = "BackupAgent" + packageName;
+ packageInfo.applicationInfo.packageName = packageName;
+ mShadowPackageManager.setApplicationEnabledSetting(
+ packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ mShadowPackageManager.addPackage(packageInfo);
+ BackupAgent backupAgent = spy(BackupAgent.class);
+ IBackupAgent backupAgentBinder =
+ spy(IBackupAgent.Stub.asInterface(backupAgent.onBind()));
+ // Don't crash our only process (in production code this would crash the app, not us)
+ doNothing().when(backupAgentBinder).fail(any());
+ when(mBackupManagerService.bindToAgentSynchronous(
+ eq(packageInfo.applicationInfo), anyInt()))
+ .thenReturn(backupAgentBinder);
+ return new AgentMock(backupAgentBinder, backupAgent);
+ } catch (RemoteException e) {
+ // Never happens, compiler happy
+ throw new AssertionError(e);
+ }
}
private PerformBackupTask createPerformBackupTask(
@@ -288,10 +394,53 @@
return task;
}
- private ArgumentMatcher<BackupDataOutput> dataOutputWithTransportFlags(int flags) {
+ private static ArgumentMatcher<PackageInfo> packageInfo(String packageName) {
+ return packageInfo -> packageName.equals(packageInfo.packageName);
+ }
+
+ private static ArgumentMatcher<BackupDataOutput> dataOutputWithTransportFlags(int flags) {
return dataOutput -> dataOutput.getTransportFlags() == flags;
}
+ private static void writeData(BackupDataOutput dataOutput, String key, byte[] data)
+ throws IOException {
+ dataOutput.writeEntityHeader(key, data.length);
+ dataOutput.writeEntityData(data, data.length);
+ }
+
+ private static void agentOnBackupDo(BackupAgent agent, BackupAgentOnBackup function)
+ throws Exception {
+ doAnswer(function).when(agent).onBackup(any(), any(), any());
+ }
+
+ @FunctionalInterface
+ private interface BackupAgentOnBackup extends Answer<Void> {
+ void onBackup(
+ ParcelFileDescriptor oldState,
+ BackupDataOutput dataOutput,
+ ParcelFileDescriptor newState)
+ throws IOException;
+
+ @Override
+ default Void answer(InvocationOnMock invocation) throws Throwable {
+ onBackup(
+ invocation.getArgument(0),
+ invocation.getArgument(1),
+ invocation.getArgument(2));
+ return null;
+ }
+ }
+
+ private static class AgentMock {
+ private final IBackupAgent agentBinder;
+ private final BackupAgent agent;
+
+ public AgentMock(IBackupAgent agentBinder, BackupAgent agent) {
+ this.agentBinder = agentBinder;
+ this.agent = agent;
+ }
+ }
+
private abstract static class FakeIBackupManager extends IBackupManager.Stub {
private Handler mBackupHandler;
private BackupRestoreTask mTask;
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
index 28489af..8016a8b 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
@@ -21,41 +21,76 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import java.io.EOFException;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.ObjectInputStream;
+/**
+ * Shadow for {@link BackupDataInput}. Format read does NOT match implementation. To write data to
+ * be read by this shadow, you should also declare shadow {@link ShadowBackupDataOutput}.
+ */
@Implements(BackupDataInput.class)
public class ShadowBackupDataInput {
- @Implementation
- public void __constructor__(FileDescriptor fd) {
- }
+ private ObjectInputStream mInput;
+ private int mSize;
+ private String mKey;
+ private boolean mHeaderReady;
@Implementation
- protected void finalize() throws Throwable {
+ public void __constructor__(FileDescriptor fd) {
+ try {
+ mInput = new ObjectInputStream(new FileInputStream(fd));
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
}
@Implementation
public boolean readNextHeader() throws IOException {
- return false;
+ mHeaderReady = false;
+ try {
+ mSize = mInput.readInt();
+ } catch (EOFException e) {
+ return false;
+ }
+ mKey = mInput.readUTF();
+ mHeaderReady = true;
+ return true;
}
@Implementation
public String getKey() {
- throw new AssertionError("Can't call because readNextHeader() returned false");
+ checkHeaderReady();
+ return mKey;
}
@Implementation
public int getDataSize() {
- throw new AssertionError("Can't call because readNextHeader() returned false");
+ checkHeaderReady();
+ return mSize;
}
@Implementation
public int readEntityData(byte[] data, int offset, int size) throws IOException {
- throw new AssertionError("Can't call because readNextHeader() returned false");
+ checkHeaderReady();
+ int result = mInput.read(data, offset, size);
+ if (result < 0) {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ return result;
}
@Implementation
public void skipEntityData() throws IOException {
- throw new AssertionError("Can't call because readNextHeader() returned false");
+ checkHeaderReady();
+ mInput.read(new byte[mSize], 0, mSize);
+ }
+
+ private void checkHeaderReady() {
+ if (!mHeaderReady) {
+ throw new IllegalStateException("Entity header not read");
+ }
}
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
index c7deada..e78a4b3 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
@@ -21,16 +21,29 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.ObjectOutputStream;
+/**
+ * Shadow for {@link BackupDataOutput}. Format written does NOT match implementation. To read data
+ * written with this shadow you should also declare shadow {@link ShadowBackupDataInput}.
+ */
@Implements(BackupDataOutput.class)
public class ShadowBackupDataOutput {
private long mQuota;
private int mTransportFlags;
+ private ObjectOutputStream mOutput;
@Implementation
public void __constructor__(FileDescriptor fd, long quota, int transportFlags) {
+ try {
+ mOutput = new ObjectOutputStream(new FileOutputStream(fd));
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
mQuota = quota;
mTransportFlags = transportFlags;
}
@@ -47,11 +60,27 @@
@Implementation
public int writeEntityHeader(String key, int dataSize) throws IOException {
- return 0;
+ final int size;
+ try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {
+ writeEntityHeader(new ObjectOutputStream(byteStream), key, dataSize);
+ size = byteStream.size();
+ }
+ writeEntityHeader(mOutput, key, dataSize);
+ return size;
+ }
+
+ private void writeEntityHeader(ObjectOutputStream stream, String key, int dataSize)
+ throws IOException {
+ // Write the int first because readInt() throws EOFException, to know when stream ends
+ stream.writeInt(dataSize);
+ stream.writeUTF(key);
+ stream.flush();
}
@Implementation
public int writeEntityData(byte[] data, int size) throws IOException {
- return 0;
+ mOutput.write(data, 0, size);
+ mOutput.flush();
+ return size;
}
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5d8aca1..97d6c43 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -60,6 +60,7 @@
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- Uses API introduced in O (26) -->
<uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index 2c50f22..a499472 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -343,6 +343,7 @@
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
mIUidObserver.onUidActive(UID_1);
+ waitUntilMainHandlerDrain();
areRestricted(instance, UID_1, PACKAGE_1, NONE);
areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
@@ -350,6 +351,7 @@
assertFalse(instance.isUidActive(UID_2));
mIUidObserver.onUidGone(UID_1, /*disable=*/ false);
+ waitUntilMainHandlerDrain();
areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
@@ -357,11 +359,13 @@
assertFalse(instance.isUidActive(UID_2));
mIUidObserver.onUidActive(UID_1);
+ waitUntilMainHandlerDrain();
areRestricted(instance, UID_1, PACKAGE_1, NONE);
areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
mIUidObserver.onUidIdle(UID_1, /*disable=*/ false);
+ waitUntilMainHandlerDrain();
areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
@@ -467,6 +471,7 @@
mIUidObserver.onUidActive(UID_1);
+ waitUntilMainHandlerDrain();
assertTrue(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -479,6 +484,7 @@
mIUidObserver.onUidStateChanged(UID_2,
ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0);
+ waitUntilMainHandlerDrain();
assertTrue(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -491,6 +497,7 @@
mIUidObserver.onUidStateChanged(UID_1,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+ waitUntilMainHandlerDrain();
assertTrue(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -501,6 +508,7 @@
mIUidObserver.onUidGone(UID_1, true);
+ waitUntilMainHandlerDrain();
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -511,6 +519,7 @@
mIUidObserver.onUidIdle(UID_2, true);
+ waitUntilMainHandlerDrain();
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -522,6 +531,7 @@
mIUidObserver.onUidStateChanged(UID_1,
ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0);
+ waitUntilMainHandlerDrain();
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -533,6 +543,7 @@
mIUidObserver.onUidStateChanged(UID_1,
ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0);
+ waitUntilMainHandlerDrain();
assertFalse(instance.isUidActive(UID_1));
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -1037,6 +1048,8 @@
mIUidObserver.onUidActive(UID_1);
mIUidObserver.onUidActive(UID_10_1);
+ waitUntilMainHandlerDrain();
+
setAppOps(UID_2, PACKAGE_2, true);
setAppOps(UID_10_2, PACKAGE_2, true);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 00e27c9..ab0bfefb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -447,5 +447,8 @@
return new TransferOwnershipMetadataManager(
new TransferOwnershipMetadataManagerTest.MockInjector());
}
+
+ @Override
+ public void runCryptoSelfTest() {}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index ce5ee13..e40e3a4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -275,7 +275,7 @@
}
@Test
- public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
+ public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception {
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
@@ -323,6 +323,26 @@
}
@Test
+ public void run_sendsEncryptedKeysIfAvailableToSync_withCertPath() throws Exception {
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+ mRecoverableKeyStoreDb.setServerParams(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
+ when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+ addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+
+ mKeySyncTask.run();
+
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
+ List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
+ assertThat(applicationKeys).hasSize(1);
+ assertThat(keyChainSnapshot.getTrustedHardwarePublicKey())
+ .isEqualTo(SecureBox.encodePublicKey(
+ TestData.CERT_PATH_1.getCertificates().get(0).getPublicKey()));
+ }
+
+ @Test
public void run_setsCorrectSnapshotVersion() throws Exception {
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index f9ffccd..0f0e3f3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -255,6 +255,9 @@
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(true);
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
@@ -264,6 +267,56 @@
}
@Test
+ public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
+
+ mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
+
+ verify(mKeyStoreProxy).containsAlias(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ // Attempt to get regenerated key.
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ any());
+ }
+
+ @Test
+ public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(false); // was removed
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
+
+ mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
+
+ verify(mKeyStoreProxy).containsAlias(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ // Attempt to get regenerated key.
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ any());
+ }
+
+ @Test
public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
@@ -274,7 +327,13 @@
+ "platform/42/1/encrypt")).thenReturn(true);
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/2/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
@@ -295,11 +354,16 @@
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed.
-
+ + "platform/42/1/encrypt")).thenReturn(true);
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true); // new version is available
+ + "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
@@ -312,14 +376,45 @@
}
@Test
- public void getEncryptKey_generatesNewKeyIfOldWasRemoved() throws Exception {
+ public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
+ doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any());
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(false); // was removed.
-
+ + "platform/42/1/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(false); // was removed
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
+
+ mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
+
+ // Attempt to get regenerated key.
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ any());
+ }
+
+ @Test
+ public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
@@ -332,10 +427,38 @@
}
@Test
- public void getEncryptKey_getsEndryptKeyWithCorrectAlias() throws Exception {
+ public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(false); // was removed
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/2/decrypt")).thenReturn(true);
+
+ mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
+
+ verify(mKeyStoreProxy).containsAlias(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ // Attempt to get regenerated key.
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ any());
+ }
+
+ @Test
+ public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/1/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 473a813..a523b86 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -125,6 +125,7 @@
private static final byte[] RECOVERY_RESPONSE_HEADER =
"V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
private static final String TEST_ALIAS = "nick";
+ private static final String TEST_ALIAS2 = "bob";
private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
private static final int GENERATION_ID = 1;
private static final byte[] NONCE = getUtf8Bytes("nonce");
@@ -237,15 +238,81 @@
}
@Test
- public void initRecoveryService_updatesShouldCreateSnapshot() throws Exception {
+ public void initRecoveryService_succeeds() throws Exception {
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
- // Sync is not needed.
+ long certSerial = 1000L;
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+ TestData.CERT_PATH_1);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(
+ certSerial);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+ }
+
+ @Test
+ public void initRecoveryService_updatesWithLargerSerial() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial + 1));
+
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid))
+ .isEqualTo(certSerial + 1);
+ }
+
+ @Test
+ public void initRecoveryService_ignoresSmallerSerial() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial - 1));
+
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid))
+ .isEqualTo(certSerial);
+ }
+
+ @Test
+ public void initRecoveryService_ignoresTheSameSerial() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+
+ // If the second update succeeds, getShouldCreateSnapshot() will return true.
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
+ }
+
+ @Test
+ public void initRecoveryService_succeedsWithRawPublicKey() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNotNull();
}
@Test
@@ -343,26 +410,6 @@
}
@Test
- public void startRecoverySession_throwsIfBadKey() throws Exception {
- try {
- mRecoverableKeyStoreManager.startRecoverySession(
- TEST_SESSION_ID,
- getUtf8Bytes("0"),
- TEST_VAULT_PARAMS,
- TEST_VAULT_CHALLENGE,
- ImmutableList.of(
- new KeyChainProtectionParams(
- TYPE_LOCKSCREEN,
- UI_FORMAT_PASSWORD,
- KeyDerivationParams.createSha256Params(TEST_SALT),
- TEST_SECRET)));
- fail("should have thrown");
- } catch (ServiceSpecificException e) {
- assertEquals("Not a valid X509 key", e.getMessage());
- }
- }
-
- @Test
public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception {
byte[] vaultParams = TEST_VAULT_PARAMS.clone();
vaultParams[1] ^= (byte) 1; // Flip 1 bit
@@ -424,7 +471,7 @@
}
@Test
- public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception {
+ public void recoverKeys_throwsIfFailedToDecryptAllApplicationKeys() throws Exception {
mRecoverableKeyStoreManager.startRecoverySession(
TEST_SESSION_ID,
TEST_PUBLIC_KEY,
@@ -442,7 +489,7 @@
keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
WrappedApplicationKey badApplicationKey = new WrappedApplicationKey(
TEST_ALIAS,
- randomBytes(32));
+ encryptedApplicationKey(randomRecoveryKey(), randomBytes(32)));
try {
mRecoverableKeyStoreManager.recoverKeys(
@@ -451,7 +498,7 @@
/*applicationKeys=*/ ImmutableList.of(badApplicationKey));
fail("should have thrown");
} catch (ServiceSpecificException e) {
- assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'");
+ assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys");
}
}
@@ -487,6 +534,44 @@
}
@Test
+ public void recoverKeys_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyChainProtectionParams(
+ TYPE_LOCKSCREEN,
+ UI_FORMAT_PASSWORD,
+ KeyDerivationParams.createSha256Params(TEST_SALT),
+ TEST_SECRET)));
+ byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
+ .getKeyClaimant();
+ SecretKey recoveryKey = randomRecoveryKey();
+ byte[] encryptedClaimResponse = encryptClaimResponse(
+ keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+
+ byte[] applicationKeyBytes1 = randomBytes(32);
+ byte[] applicationKeyBytes2 = randomBytes(32);
+
+ WrappedApplicationKey applicationKey1 = new WrappedApplicationKey(
+ TEST_ALIAS,
+ // Use a different recovery key here, so the decryption will fail
+ encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1));
+ WrappedApplicationKey applicationKey2 = new WrappedApplicationKey(
+ TEST_ALIAS2,
+ encryptedApplicationKey(recoveryKey, applicationKeyBytes2));
+
+ Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
+ TEST_SESSION_ID,
+ encryptedClaimResponse,
+ ImmutableList.of(applicationKey1, applicationKey2));
+
+ assertThat(recoveredKeys).hasSize(1);
+ assertThat(recoveredKeys.get(TEST_ALIAS2)).isEqualTo(applicationKeyBytes2);
+ }
+
+ @Test
public void setSnapshotCreatedPendingIntent() throws Exception {
int uid = Binder.getCallingUid();
PendingIntent intent = PendingIntent.getBroadcast(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
new file mode 100644
index 0000000..0e4f91b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
@@ -0,0 +1,202 @@
+package com.android.server.locksettings.recoverablekeystore;
+
+import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateFactory;
+
+public final class TestData {
+
+ private static final String CERT_PATH_ENCODING = "PkiPath";
+
+ private static final String CERT_PATH_1_BASE64 = ""
+ + "MIIIPzCCBS8wggMXoAMCAQICAhAAMA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNVBAMM"
+ + "FUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDMwMDQyMDNaFw0yODAyMDEw"
+ + "MDQyMDNaMC0xKzApBgNVBAMMIkdvb2dsZSBDcnlwdEF1dGhWYXVsdCBJbnRlcm1l"
+ + "ZGlhdGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDckHib0X6rQyDq"
+ + "k4519b5du0OrCPk30XXKwz+Hz5y4cGZaWKGcHOHWS2X9YApRzO00/EbvFkWVUTVG"
+ + "27wJ54V+C3HHSOAUWHhEgfFWvvHwfn9HTDx1BEk79aQqJ7DuJ06Sn/WOiMtKVAT5"
+ + "6Mi8mekBxpMOrdZqwlcLrUVsZxEHsw5/ceZu4cSWzc7SzlnbNK1cCgyRDGqWf6Gp"
+ + "3hGE86kUOtM1i95RgUIpw+w/z0wxpF6kIyQTjK+KjiYH/RBOJIEcm6sSWZlMotKL"
+ + "Sn2lhf+XL8yUxExIHTosfeb077QWW4w2BB2NZM4wPAO3w4aw33FNigDQc2SQYmnU"
+ + "EYmIcD8kx77+JWCgCxBJc2zTHXtBxWuXAQ+iegt8RO+QD97pd6XKM9xPsAOkcWLp"
+ + "79o+AJol4P5fwvgYM69mM4lwH12v86RI4aptPQOag0KDIHXyKbjaQyAgv30l4KkD"
+ + "pf2uWODhOOTwNbVPYUm3sYUlhBcbyhTk8YqN9sPU4QAao5sKTAYZgB/mlheQypTU"
+ + "wyvqz6bRzGehVB3ltP9gCyKdI04VXEUuUBWk3STyV2REQen5/LKAns6v11Cz22Zr"
+ + "EdCvNLgetnyV7CJsOa/wD/GiUWL2Ta7pzshi9ahJqrrcNPRbAzOLcNKZkFexhzPp"
+ + "onuo/pNrcaRda1frepXxVkmbsgOULwIDAQABo2YwZDAdBgNVHQ4EFgQUd6md2hCP"
+ + "lmf3VkEX5FfDxKBLbaAwHwYDVR0jBBgwFoAUm2X66jmB+eBCaZHSjGYzHM/x6fgw"
+ + "EgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL"
+ + "BQADggIBAFgShhuW+WVTowN080PLf0TWPlHACHHUPghf7rFGxgUjJypCloE84Beg"
+ + "3ROpP5l19CDqZ9OyPzA1z6VAzeGXyFhZvby7G2tZDBRP/v0u8pnSAdC5F8l8Vh2Y"
+ + "GdgE3sZD25vpdBi7P0Ef6LYQetOJXn86PqgmgW1F6lzxDjKCsi9kpeU0AWwDdOVg"
+ + "748wku50o8UEzsVwxzFd9toGlge/nn3FH5J7EuGzAlFwToHqpwTVEegaAd0l9mr5"
+ + "+rS7Urd3X80BHDqCBcXE7Uqbtzw5Y+lmowMCnW0kFN02dC9dLt2c9IxC+9sPIA5e"
+ + "TkrZBkrkTVRGLj2r29j7nC9m5VaKcBqcLZDWy8pRna8yaZprgNdE8d/WTY9nVsic"
+ + "09N8zNF5Q0bhhWa3QonlB9XW5ZqDguiclvn+5TtREzSAtSOyxM+gfG3l0wjOywIk"
+ + "1aFa52RaqAWPL67KOM6G3vKNpMnW5hrmHrijuKxiarGIoZfkZMR5ijK0uFgv3/p6"
+ + "NHL/YQBaHJJhkKet5ThiPxwW9+1k/ZcXVeY26Xh+22Gp/8to7ZW8guPPiN1hfpD+"
+ + "7f1IdSmHDrsZQQ7bfzV0bppsyNNB7e2Ecyw+GQny27nytBLJDGdRBurbwQvzppQO"
+ + "6Qmlk0rfCszh7bGCoCQNxXmuDsQ5BC+pQUqJplTqds1smyi29xs3MIIDCDCB8aAD"
+ + "AgECAgYBYVkuU0cwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwiR29vZ2xlIENy"
+ + "eXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAyMDIwMTAxMDNaFw0yMDAy"
+ + "MDMwMTAxMDNaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhWYXVsdCBJbnN0"
+ + "YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfButJT+htocB40B"
+ + "tDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl4b0hRH54Uvow"
+ + "DQYJKoZIhvcNAQELBQADggIBAJ3PM4GNTNYzMr8E/IGsWZkLx9ARAALqBXz7As59"
+ + "F8y5UcLMqkXD/ewOfBZgF5VzjlAePyE/wSw0wc3xzvrDVVDiZaMBW1DVtSlbn25q"
+ + "00m00mmcUeyyMc7vuRkPoDshIMQTc8+U3yyYsVScSV+B4TvSx6wPZ9FpwnSPjVPD"
+ + "2GkqeMTWszuxNVEWq0wmm0K5lMaX0hfiak+4/IZxOPPGIg2py1KLA/H2gdyeqyJR"
+ + "cAsyEkfwLlushR5T9abSiPsIRcYoX8Ck8Lt+gQ7RCMefnm8CoOBKIfcjuV4PGOoe"
+ + "Xrq57VR5SsOeT07bL+D7B+mohYFI1v2G3WClAE8XgM3q8NoFFvaYmoi0+UcTduil"
+ + "47qvozjdNmjRAgu5j6vMKXEdG5Rqsja8hy0LG1hwfnR0gNiwcZ5Le3GyFnwH1Igq"
+ + "vsGOUM0ohnDUAU0zJY7nG0QYrDYe5/QPRNhWDpYkwHDiqcG28wIQCOTPAZHU2EoS"
+ + "KjSqEG2l0S5JPcor2BEde9ikSkcmK8foxlOHIdFn+n7RNF3bSEfKn1IOuXoqPidm"
+ + "eBQLevqG8KTy/C9CHqlaCNlpbIA9h+WVfsjm2s6JXBu0YbcfoIbJAmSuZVeqB/+Z"
+ + "Vvpfiad/jQWzY49fRnsSmV7VveTFPGtJxC89EadbMAinMZo+72u59319RqN5wsP2"
+ + "Zus8";
+ private static String CERT_PATH_2_BASE64 = ""
+ + "MIIFMzCCBS8wggMXoAMCAQICAhAAMA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNVBAMM"
+ + "FUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDMwMDQyMDNaFw0yODAyMDEw"
+ + "MDQyMDNaMC0xKzApBgNVBAMMIkdvb2dsZSBDcnlwdEF1dGhWYXVsdCBJbnRlcm1l"
+ + "ZGlhdGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDckHib0X6rQyDq"
+ + "k4519b5du0OrCPk30XXKwz+Hz5y4cGZaWKGcHOHWS2X9YApRzO00/EbvFkWVUTVG"
+ + "27wJ54V+C3HHSOAUWHhEgfFWvvHwfn9HTDx1BEk79aQqJ7DuJ06Sn/WOiMtKVAT5"
+ + "6Mi8mekBxpMOrdZqwlcLrUVsZxEHsw5/ceZu4cSWzc7SzlnbNK1cCgyRDGqWf6Gp"
+ + "3hGE86kUOtM1i95RgUIpw+w/z0wxpF6kIyQTjK+KjiYH/RBOJIEcm6sSWZlMotKL"
+ + "Sn2lhf+XL8yUxExIHTosfeb077QWW4w2BB2NZM4wPAO3w4aw33FNigDQc2SQYmnU"
+ + "EYmIcD8kx77+JWCgCxBJc2zTHXtBxWuXAQ+iegt8RO+QD97pd6XKM9xPsAOkcWLp"
+ + "79o+AJol4P5fwvgYM69mM4lwH12v86RI4aptPQOag0KDIHXyKbjaQyAgv30l4KkD"
+ + "pf2uWODhOOTwNbVPYUm3sYUlhBcbyhTk8YqN9sPU4QAao5sKTAYZgB/mlheQypTU"
+ + "wyvqz6bRzGehVB3ltP9gCyKdI04VXEUuUBWk3STyV2REQen5/LKAns6v11Cz22Zr"
+ + "EdCvNLgetnyV7CJsOa/wD/GiUWL2Ta7pzshi9ahJqrrcNPRbAzOLcNKZkFexhzPp"
+ + "onuo/pNrcaRda1frepXxVkmbsgOULwIDAQABo2YwZDAdBgNVHQ4EFgQUd6md2hCP"
+ + "lmf3VkEX5FfDxKBLbaAwHwYDVR0jBBgwFoAUm2X66jmB+eBCaZHSjGYzHM/x6fgw"
+ + "EgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL"
+ + "BQADggIBAFgShhuW+WVTowN080PLf0TWPlHACHHUPghf7rFGxgUjJypCloE84Beg"
+ + "3ROpP5l19CDqZ9OyPzA1z6VAzeGXyFhZvby7G2tZDBRP/v0u8pnSAdC5F8l8Vh2Y"
+ + "GdgE3sZD25vpdBi7P0Ef6LYQetOJXn86PqgmgW1F6lzxDjKCsi9kpeU0AWwDdOVg"
+ + "748wku50o8UEzsVwxzFd9toGlge/nn3FH5J7EuGzAlFwToHqpwTVEegaAd0l9mr5"
+ + "+rS7Urd3X80BHDqCBcXE7Uqbtzw5Y+lmowMCnW0kFN02dC9dLt2c9IxC+9sPIA5e"
+ + "TkrZBkrkTVRGLj2r29j7nC9m5VaKcBqcLZDWy8pRna8yaZprgNdE8d/WTY9nVsic"
+ + "09N8zNF5Q0bhhWa3QonlB9XW5ZqDguiclvn+5TtREzSAtSOyxM+gfG3l0wjOywIk"
+ + "1aFa52RaqAWPL67KOM6G3vKNpMnW5hrmHrijuKxiarGIoZfkZMR5ijK0uFgv3/p6"
+ + "NHL/YQBaHJJhkKet5ThiPxwW9+1k/ZcXVeY26Xh+22Gp/8to7ZW8guPPiN1hfpD+"
+ + "7f1IdSmHDrsZQQ7bfzV0bppsyNNB7e2Ecyw+GQny27nytBLJDGdRBurbwQvzppQO"
+ + "6Qmlk0rfCszh7bGCoCQNxXmuDsQ5BC+pQUqJplTqds1smyi29xs3";
+
+ private static final String THM_CERT_XML_BEFORE_SERIAL = ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<certificates>\n"
+ + " <metadata>\n"
+ + " <serial>\n";
+ private static final String THM_CERT_XML_AFTER_SERIAL = ""
+ + " </serial>\n"
+ + " <creation-time>\n"
+ + " 1515697631\n"
+ + " </creation-time>\n"
+ + " <refresh-interval>\n"
+ + " 2592000\n"
+ + " </refresh-interval>\n"
+ + " <previous>\n"
+ + " <serial>\n"
+ + " 0\n"
+ + " </serial>\n"
+ + " <hash>\n"
+ + " 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\n"
+ + " </hash>\n"
+ + " </previous>\n"
+ + " </metadata>\n"
+ + " <intermediates>\n"
+ + " <cert>\n"
+ + " MIIFLzCCAxegAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UEAwwVR29v\n"
+ + " Z2xlIENyeXB0QXV0aFZhdWx0MB4XDTE4MDIwMzAwNDIwM1oXDTI4MDIwMTAwNDIw\n"
+ + " M1owLTErMCkGA1UEAwwiR29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0\n"
+ + " ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANyQeJvRfqtDIOqTjnX1\n"
+ + " vl27Q6sI+TfRdcrDP4fPnLhwZlpYoZwc4dZLZf1gClHM7TT8Ru8WRZVRNUbbvAnn\n"
+ + " hX4LccdI4BRYeESB8Va+8fB+f0dMPHUESTv1pConsO4nTpKf9Y6Iy0pUBPnoyLyZ\n"
+ + " 6QHGkw6t1mrCVwutRWxnEQezDn9x5m7hxJbNztLOWds0rVwKDJEMapZ/oaneEYTz\n"
+ + " qRQ60zWL3lGBQinD7D/PTDGkXqQjJBOMr4qOJgf9EE4kgRybqxJZmUyi0otKfaWF\n"
+ + " /5cvzJTETEgdOix95vTvtBZbjDYEHY1kzjA8A7fDhrDfcU2KANBzZJBiadQRiYhw\n"
+ + " PyTHvv4lYKALEElzbNMde0HFa5cBD6J6C3xE75AP3ul3pcoz3E+wA6RxYunv2j4A\n"
+ + " miXg/l/C+Bgzr2YziXAfXa/zpEjhqm09A5qDQoMgdfIpuNpDICC/fSXgqQOl/a5Y\n"
+ + " 4OE45PA1tU9hSbexhSWEFxvKFOTxio32w9ThABqjmwpMBhmAH+aWF5DKlNTDK+rP\n"
+ + " ptHMZ6FUHeW0/2ALIp0jThVcRS5QFaTdJPJXZERB6fn8soCezq/XULPbZmsR0K80\n"
+ + " uB62fJXsImw5r/AP8aJRYvZNrunOyGL1qEmqutw09FsDM4tw0pmQV7GHM+mie6j+\n"
+ + " k2txpF1rV+t6lfFWSZuyA5QvAgMBAAGjZjBkMB0GA1UdDgQWBBR3qZ3aEI+WZ/dW\n"
+ + " QRfkV8PEoEttoDAfBgNVHSMEGDAWgBSbZfrqOYH54EJpkdKMZjMcz/Hp+DASBgNV\n"
+ + " HRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\n"
+ + " AgEAWBKGG5b5ZVOjA3TzQ8t/RNY+UcAIcdQ+CF/usUbGBSMnKkKWgTzgF6DdE6k/\n"
+ + " mXX0IOpn07I/MDXPpUDN4ZfIWFm9vLsba1kMFE/+/S7ymdIB0LkXyXxWHZgZ2ATe\n"
+ + " xkPbm+l0GLs/QR/othB604lefzo+qCaBbUXqXPEOMoKyL2Sl5TQBbAN05WDvjzCS\n"
+ + " 7nSjxQTOxXDHMV322gaWB7+efcUfknsS4bMCUXBOgeqnBNUR6BoB3SX2avn6tLtS\n"
+ + " t3dfzQEcOoIFxcTtSpu3PDlj6WajAwKdbSQU3TZ0L10u3Zz0jEL72w8gDl5OStkG\n"
+ + " SuRNVEYuPavb2PucL2blVopwGpwtkNbLylGdrzJpmmuA10Tx39ZNj2dWyJzT03zM\n"
+ + " 0XlDRuGFZrdCieUH1dblmoOC6JyW+f7lO1ETNIC1I7LEz6B8beXTCM7LAiTVoVrn\n"
+ + " ZFqoBY8vrso4zobe8o2kydbmGuYeuKO4rGJqsYihl+RkxHmKMrS4WC/f+no0cv9h\n"
+ + " AFockmGQp63lOGI/HBb37WT9lxdV5jbpeH7bYan/y2jtlbyC48+I3WF+kP7t/Uh1\n"
+ + " KYcOuxlBDtt/NXRummzI00Ht7YRzLD4ZCfLbufK0EskMZ1EG6tvBC/OmlA7pCaWT\n"
+ + " St8KzOHtsYKgJA3Fea4OxDkEL6lBSommVOp2zWybKLb3Gzc=\n"
+ + " </cert>\n"
+ + " </intermediates>\n"
+ + " <endpoints>\n"
+ + " <cert>\n"
+ + " MIIDCDCB8aADAgECAgYBYVkuU0cwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi\n"
+ + " R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAyMDIwMTAx\n"
+ + " MDNaFw0yMDAyMDMwMTAxMDNaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW\n"
+ + " YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu\n"
+ + " tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl\n"
+ + " 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBAJ3PM4GNTNYzMr8E/IGsWZkLx9AR\n"
+ + " AALqBXz7As59F8y5UcLMqkXD/ewOfBZgF5VzjlAePyE/wSw0wc3xzvrDVVDiZaMB\n"
+ + " W1DVtSlbn25q00m00mmcUeyyMc7vuRkPoDshIMQTc8+U3yyYsVScSV+B4TvSx6wP\n"
+ + " Z9FpwnSPjVPD2GkqeMTWszuxNVEWq0wmm0K5lMaX0hfiak+4/IZxOPPGIg2py1KL\n"
+ + " A/H2gdyeqyJRcAsyEkfwLlushR5T9abSiPsIRcYoX8Ck8Lt+gQ7RCMefnm8CoOBK\n"
+ + " IfcjuV4PGOoeXrq57VR5SsOeT07bL+D7B+mohYFI1v2G3WClAE8XgM3q8NoFFvaY\n"
+ + " moi0+UcTduil47qvozjdNmjRAgu5j6vMKXEdG5Rqsja8hy0LG1hwfnR0gNiwcZ5L\n"
+ + " e3GyFnwH1IgqvsGOUM0ohnDUAU0zJY7nG0QYrDYe5/QPRNhWDpYkwHDiqcG28wIQ\n"
+ + " COTPAZHU2EoSKjSqEG2l0S5JPcor2BEde9ikSkcmK8foxlOHIdFn+n7RNF3bSEfK\n"
+ + " n1IOuXoqPidmeBQLevqG8KTy/C9CHqlaCNlpbIA9h+WVfsjm2s6JXBu0YbcfoIbJ\n"
+ + " AmSuZVeqB/+ZVvpfiad/jQWzY49fRnsSmV7VveTFPGtJxC89EadbMAinMZo+72u5\n"
+ + " 9319RqN5wsP2Zus8\n"
+ + " </cert>\n"
+ + " </endpoints>\n"
+ + "</certificates>\n";
+
+ public static byte[] getCertPath1Bytes() {
+ try {
+ return CertUtils.decodeBase64(CERT_PATH_1_BASE64);
+ } catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static byte[] getCertPath2Bytes() {
+ try {
+ return CertUtils.decodeBase64(CERT_PATH_2_BASE64);
+ } catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static final CertPath CERT_PATH_1;
+ public static final CertPath CERT_PATH_2;
+
+ static {
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ CERT_PATH_1 = certFactory.generateCertPath(
+ new ByteArrayInputStream(getCertPath1Bytes()), CERT_PATH_ENCODING);
+ CERT_PATH_2 = certFactory.generateCertPath(
+ new ByteArrayInputStream(getCertPath2Bytes()), CERT_PATH_ENCODING);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static byte[] getCertXmlWithSerial(long serial) {
+ String xml = THM_CERT_XML_BEFORE_SERIAL + serial + THM_CERT_XML_AFTER_SERIAL;
+ return xml.getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java
index ac6d293..d08dab4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java
@@ -146,14 +146,14 @@
@Test
public void getXmlNodeContents_singleLevel_succeeds() throws Exception {
Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
- assertThat(CertUtils.getXmlNodeContents(CertUtils.MustExist.FALSE, root, "node1"))
+ assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node1"))
.containsExactly("node1-1", "node1-2");
}
@Test
public void getXmlNodeContents_multipleLevels_succeeds() throws Exception {
Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
- assertThat(CertUtils.getXmlNodeContents(CertUtils.MustExist.FALSE, root, "node2", "node1"))
+ assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node1"))
.containsExactly("node2-node1-1", "node2-node1-2", "node2-node1-3");
}
@@ -162,7 +162,7 @@
Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8));
assertThat(
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.FALSE, root, "node2", "node-not-exist"))
+ CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node-not-exist"))
.isEmpty();
}
@@ -174,7 +174,7 @@
CertParsingException.class,
() ->
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.AT_LEAST_ONE, root, "node2",
+ CertUtils.MUST_EXIST_AT_LEAST_ONE, root, "node2",
"node-not-exist"));
assertThat(expected.getMessage()).contains("must contain at least one");
}
@@ -187,7 +187,7 @@
CertParsingException.class,
() ->
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.EXACTLY_ONE, root, "node-not-exist",
+ CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node-not-exist",
"node1"));
assertThat(expected.getMessage()).contains("must contain exactly one");
}
@@ -200,7 +200,7 @@
CertParsingException.class,
() ->
CertUtils.getXmlNodeContents(
- CertUtils.MustExist.EXACTLY_ONE, root, "node2", "node1"));
+ CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node2", "node1"));
assertThat(expected.getMessage()).contains("must contain exactly one");
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
new file mode 100644
index 0000000..37482a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreDbHelperTest {
+
+ private static final long TEST_USER_ID = 10L;
+ private static final long TEST_UID = 60001L;
+ private static final String TEST_ALIAS = "test-alias";
+ private static final byte[] TEST_NONCE = "test-nonce".getBytes(UTF_8);
+ private static final byte[] TEST_WRAPPED_KEY = "test-wrapped-key".getBytes(UTF_8);
+ private static final long TEST_GENERATION_ID = 13L;
+ private static final long TEST_LAST_SYNCED_AT = 1517990732000L;
+ private static final int TEST_RECOVERY_STATUS = 3;
+ private static final int TEST_PLATFORM_KEY_GENERATION_ID = 11;
+ private static final int TEST_SNAPSHOT_VERSION = 31;
+ private static final int TEST_SHOULD_CREATE_SNAPSHOT = 1;
+ private static final byte[] TEST_PUBLIC_KEY = "test-public-key".getBytes(UTF_8);
+ private static final String TEST_SECRET_TYPES = "test-secret-types";
+ private static final long TEST_COUNTER_ID = -3981205205038476415L;
+ private static final byte[] TEST_SERVER_PARAMS = "test-server-params".getBytes(UTF_8);
+ private static final byte[] TEST_CERT_PATH = "test-cert-path".getBytes(UTF_8);
+ private static final long TEST_CERT_SERIAL = 1000L;
+
+ private static final String SQL_CREATE_V2_TABLE_KEYS =
+ "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+ + KeysEntry._ID + " INTEGER PRIMARY KEY,"
+ + KeysEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
+ + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
+ + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
+ + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER,"
+ + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
+ + KeysEntry.COLUMN_NAME_ALIAS + "))";
+
+ private static final String SQL_CREATE_V2_TABLE_USER_METADATA =
+ "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+ + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+ + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+
+ private static final String SQL_CREATE_V2_TABLE_RECOVERY_SERVICE_METADATA =
+ "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
+ + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
+ + "UNIQUE("
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + ","
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
+
+ private SQLiteDatabase mDatabase;
+ private RecoverableKeyStoreDbHelper mDatabaseHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseHelper = new RecoverableKeyStoreDbHelper(context);
+ mDatabase = SQLiteDatabase.create(null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mDatabase.close();
+ }
+
+ private void createV2Tables() throws Exception {
+ mDatabase.execSQL(SQL_CREATE_V2_TABLE_KEYS);
+ mDatabase.execSQL(SQL_CREATE_V2_TABLE_USER_METADATA);
+ mDatabase.execSQL(SQL_CREATE_V2_TABLE_RECOVERY_SERVICE_METADATA);
+ }
+
+ @Test
+ public void onCreate() throws Exception {
+ mDatabaseHelper.onCreate(mDatabase);
+ checkAllColumns();
+ }
+
+ @Test
+ public void onUpgrade_beforeV2() throws Exception {
+ mDatabaseHelper.onUpgrade(mDatabase, /*oldVersion=*/ 1,
+ RecoverableKeyStoreDbHelper.DATABASE_VERSION);
+ checkAllColumns();
+ }
+
+ @Test
+ public void onUpgrade_fromV2() throws Exception {
+ createV2Tables();
+ mDatabaseHelper.onUpgrade(mDatabase, /*oldVersion=*/ 2,
+ RecoverableKeyStoreDbHelper.DATABASE_VERSION);
+ checkAllColumns();
+ }
+
+ private void checkAllColumns() throws Exception {
+ // Check the table containing encrypted application keys
+ ContentValues values = new ContentValues();
+ values.put(KeysEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(KeysEntry.COLUMN_NAME_UID, TEST_UID);
+ values.put(KeysEntry.COLUMN_NAME_ALIAS, TEST_ALIAS);
+ values.put(KeysEntry.COLUMN_NAME_NONCE, TEST_NONCE);
+ values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, TEST_WRAPPED_KEY);
+ values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, TEST_GENERATION_ID);
+ values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, TEST_LAST_SYNCED_AT);
+ values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, TEST_RECOVERY_STATUS);
+ assertThat(mDatabase.insert(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values))
+ .isGreaterThan(-1L);
+
+ // Check the table about user metadata
+ values = new ContentValues();
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID,
+ TEST_PLATFORM_KEY_GENERATION_ID);
+ assertThat(mDatabase.insert(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values))
+ .isGreaterThan(-1L);
+
+ // Check the table about recovery service metadata
+ values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, TEST_UID);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION,
+ TEST_SNAPSHOT_VERSION);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT,
+ TEST_SHOULD_CREATE_SNAPSHOT);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, TEST_PUBLIC_KEY);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, TEST_SECRET_TYPES);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, TEST_COUNTER_ID);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, TEST_SERVER_PARAMS);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, TEST_CERT_PATH);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, TEST_CERT_SERIAL);
+ assertThat(
+ mDatabase.insert(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+ values))
+ .isGreaterThan(-1L);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 097d214..1c5bcd4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -32,6 +32,8 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.TestData;
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import java.io.File;
@@ -370,6 +372,57 @@
pubkey);
}
+ public void setRecoveryServiceCertPath_replaceOldValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_1);
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_2);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+ TestData.CERT_PATH_2);
+ }
+
+ @Test
+ public void getRecoveryServiceCertPath_returnsNullIfNoValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getRecoveryServiceCertPath_returnsInsertedValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_1);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+ TestData.CERT_PATH_1);
+ }
+
+ @Test
+ public void setRecoveryServiceCertSerial_replaceOldValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 1L);
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 3L);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(3L);
+ }
+
+ @Test
+ public void getRecoveryServiceCertSerial_returnsNullIfNoValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getRecoveryServiceCertSerial_returnsInsertedValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 1234L);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(
+ 1234L);
+ }
+
@Test
public void getRecoveryAgents_returnsUidIfSet() throws Exception {
int userId = 12;
@@ -493,17 +546,6 @@
}
@Test
- public void getRecoveryServicePublicKey_returnsFirstKey() throws Exception {
- int userId = 68;
- int uid = 12904;
- PublicKey publicKey = genRandomPublicKey();
-
- mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, publicKey);
-
- assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId)).isEqualTo(publicKey);
- }
-
- @Test
public void setServerParams_replaceOldValue() throws Exception {
int userId = 12;
int uid = 10009;
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
index 070de5b..a38b353 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
@@ -16,18 +16,43 @@
package com.android.server.net.watchlist;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doAnswer;
+
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.FileUtils;
+import android.os.Looper;
+import android.os.UserManager;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
/**
* runtest frameworks-services -c com.android.server.net.watchlist.WatchlistLoggingHandlerTests
@@ -36,18 +61,126 @@
@SmallTest
public class WatchlistLoggingHandlerTests {
+ private static final String APK_A = "A.apk";
+ private static final String APK_B = "B.apk";
+ private static final String APK_A_CONTENT = "AAA";
+ private static final String APK_B_CONTENT = "BBB";
+ // Sha256 of "AAA"
+ private static final String APK_A_CONTENT_HASH =
+ "CB1AD2119D8FAFB69566510EE712661F9F14B83385006EF92AEC47F523A38358";
+ // Sha256 of "BBB"
+ private static final String APK_B_CONTENT_HASH =
+ "DCDB704109A454784B81229D2B05F368692E758BFA33CB61D04C1B93791B0273";
+
+ private Context mServiceContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ final Context context = InstrumentationRegistry.getContext();
+ final UserManager mockUserManager = mock(UserManager.class);
+ final PackageManager mockPackageManager = mock(PackageManager.class);
+
+ // Context that will be used by WatchlistLoggingHandler
+ mServiceContext = new ContextWrapper(context) {
+ @Override
+ public PackageManager getPackageManager() {
+ return mockPackageManager;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case Context.USER_SERVICE:
+ return mockUserManager;
+ default:
+ return super.getSystemService(name);
+ }
+ }
+ };
+
+ // Returns 2 users, user 0 and user 10
+ doAnswer((InvocationOnMock invocation) -> {
+ final ArrayList<UserInfo> info = new ArrayList<>();
+ info.add(new UserInfo(0, "user1", 0));
+ info.add(new UserInfo(10, "user2", 0));
+ return info;
+ }).when(mockUserManager).getUsers();
+
+ // Returns 2 apps, with uid 1 and uid 2
+ doAnswer((InvocationOnMock invocation) -> {
+ final List<ApplicationInfo> result = new ArrayList<>();
+ ApplicationInfo info1 = new ApplicationInfo();
+ info1.uid = 1;
+ result.add(info1);
+ ApplicationInfo info2 = new ApplicationInfo();
+ info2.uid = 2;
+ result.add(info2);
+ return result;
+ }).when(mockPackageManager).getInstalledApplications(anyInt());
+
+ // Uid 1 app with is installed in primary user and package name is "A"
+ // Uid 2 app is installed in secondary user and package name is "B"
+ doAnswer((InvocationOnMock invocation) -> {
+ int uid = (int) invocation.getArguments()[0];
+ if (uid == 1) {
+ return new String[]{"A"};
+ } else if (uid == 1000001) {
+ return null;
+ } else if (uid == 2) {
+ return null;
+ } else if (uid == 1000002) {
+ return new String[]{"B"};
+ }
+ return null;
+ }).when(mockPackageManager).getPackagesForUid(anyInt());
+
+ String fileDir = InstrumentationRegistry.getContext().getFilesDir().getAbsolutePath();
+ // Simulate app's apk file path
+ doAnswer((InvocationOnMock invocation) -> {
+ String pkg = (String) invocation.getArguments()[0];
+ PackageInfo result = new PackageInfo();
+ result.applicationInfo = new ApplicationInfo();
+ result.applicationInfo.publicSourceDir = fileDir + "/" + pkg + ".apk";
+ return result;
+ }).when(mockPackageManager).getPackageInfoAsUser(anyString(), anyInt(), anyInt());
+
+ FileUtils.bytesToFile(fileDir + "/" + APK_A, APK_A_CONTENT.getBytes());
+ FileUtils.bytesToFile(fileDir + "/" + APK_B, APK_B_CONTENT.getBytes());
+ }
+
+ @After
+ public void tearDown() {
+ String fileDir = InstrumentationRegistry.getContext().getFilesDir().getAbsolutePath();
+ new File(fileDir, APK_A).delete();
+ new File(fileDir, APK_B).delete();
+ }
+
+ @Test
+ public void testWatchlistLoggingHandler_getAllDigestsForReportWithMultiUsers()
+ throws Exception {
+ List<String> result = new WatchlistLoggingHandler(mServiceContext,
+ Looper.getMainLooper()).getAllDigestsForReport(
+ new WatchlistReportDbHelper.AggregatedResult(new HashSet<String>(), null,
+ new HashMap<String, String>()));
+ assertEquals(2, result.size());
+ assertEquals(APK_A_CONTENT_HASH, result.get(0));
+ assertEquals(APK_B_CONTENT_HASH, result.get(1));
+ }
+
@Test
public void testWatchlistLoggingHandler_getAllSubDomains() throws Exception {
String[] subDomains = WatchlistLoggingHandler.getAllSubDomains("abc.def.gh.i.jkl.mm");
- assertTrue(Arrays.equals(subDomains, new String[] {"abc.def.gh.i.jkl.mm",
+ assertTrue(Arrays.equals(subDomains, new String[]{"abc.def.gh.i.jkl.mm",
"def.gh.i.jkl.mm", "gh.i.jkl.mm", "i.jkl.mm", "jkl.mm", "mm"}));
subDomains = WatchlistLoggingHandler.getAllSubDomains(null);
assertNull(subDomains);
subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm");
- assertTrue(Arrays.equals(subDomains, new String[] {"jkl.mm", "mm"}));
+ assertTrue(Arrays.equals(subDomains, new String[]{"jkl.mm", "mm"}));
subDomains = WatchlistLoggingHandler.getAllSubDomains("abc");
- assertTrue(Arrays.equals(subDomains, new String[] {"abc"}));
+ assertTrue(Arrays.equals(subDomains, new String[]{"abc"}));
subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm.");
- assertTrue(Arrays.equals(subDomains, new String[] {"jkl.mm.", "mm."}));
+ assertTrue(Arrays.equals(subDomains, new String[]{"jkl.mm.", "mm."}));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 0abb48f..98483a8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -294,7 +294,7 @@
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
- null /*resourcePath*/,
+ UPDATED_CODE_PATH /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
@@ -328,7 +328,7 @@
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
- null /*resourcePath*/,
+ UPDATED_CODE_PATH /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
@@ -561,34 +561,6 @@
false /*notLaunched*/, false /*stopped*/, true /*installed*/);
}
- @Test
- public void testInsertPackageSetting() {
- final PackageSetting ps = createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
- final PackageParser.Package pkg = new PackageParser.Package(PACKAGE_NAME);
- pkg.applicationInfo.setCodePath(ps.codePathString);
- pkg.applicationInfo.setResourcePath(ps.resourcePathString);
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
- final Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
- pkg.usesStaticLibraries = new ArrayList<>(
- Arrays.asList("foo.bar1", "foo.bar2", "foo.bar3"));
- pkg.usesStaticLibrariesVersions = new long[] {2, 4, 6};
- settings.insertPackageSettingLPw(ps, pkg);
- assertEquals(pkg, ps.pkg);
- assertArrayEquals(pkg.usesStaticLibraries.toArray(new String[0]), ps.usesStaticLibraries);
- assertArrayEquals(pkg.usesStaticLibrariesVersions, ps.usesStaticLibrariesVersions);
-
- pkg.usesStaticLibraries = null;
- pkg.usesStaticLibrariesVersions = null;
- settings.insertPackageSettingLPw(ps, pkg);
- assertEquals(pkg, ps.pkg);
- assertNull("Actual: " + Arrays.toString(ps.usesStaticLibraries), ps.usesStaticLibraries);
- assertNull("Actual: " + Arrays.toString(ps.usesStaticLibrariesVersions),
- ps.usesStaticLibrariesVersions);
- }
-
private <T> void assertArrayEquals(T[] a, T[] b) {
assertTrue("Expected: " + Arrays.toString(a) + ", actual: " + Arrays.toString(b),
Arrays.equals(a, b));
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
index 5de393c..a628b7b 100644
--- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -79,6 +79,11 @@
}
@Override
+ public Point getShownPositionLw() {
+ return new Point(parentFrame.left, parentFrame.top);
+ }
+
+ @Override
public Rect getDisplayFrameLw() {
return displayFrame;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 2284bbb..63ac4af 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -28,6 +28,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -38,7 +39,6 @@
import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.graphics.Path;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -166,6 +166,7 @@
assertTrue(appWin.canBeImeTarget());
WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
assertEquals(appWin, imeTarget);
+ appWin.mHidden = false;
// Verify that an child window can be an ime target.
final WindowState childWin = createWindow(appWin,
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
index f23bd62..3a1485e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.graphics.Color.BLUE;
import static android.graphics.Color.RED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
@@ -27,7 +26,6 @@
import static android.view.Gravity.RIGHT;
import static android.view.Gravity.TOP;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -49,6 +47,7 @@
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.Pair;
@@ -66,18 +65,19 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.function.BooleanSupplier;
/**
* Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag.
*
* Build/Install/Run:
- * bit FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests
+ * atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests
*/
// TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags.
// TODO: Test non-Activity windows.
@SmallTest
-// TODO(b/68957554)
-//@Presubmit
+@Presubmit
+@FlakyTest(bugId = 68957554)
@RunWith(AndroidJUnit4.class)
public class ScreenDecorWindowTests {
@@ -123,40 +123,33 @@
public void testScreenSides() throws Exception {
// Decor on top
final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
- WindowInsets insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
// Decor at the bottom
updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0);
- insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness);
// Decor to the left
updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0);
- insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness);
// Decor to the right
updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0);
- insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetRight(), mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness);
}
@Test
public void testMultipleDecors() throws Exception {
// Test 2 decor windows on-top.
createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness);
- WindowInsets insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mHalfDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness);
createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
- insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
// And one at the bottom.
createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness);
- insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
- assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mHalfDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness);
}
@Test
@@ -164,18 +157,15 @@
WindowInsets initialInsets = getInsets(mTestActivity);
final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
- WindowInsets insets = getInsets(mTestActivity);
- assertEquals(mDecorThickness, insets.getSystemWindowInsetTop());
+ assertTopInsetEquals(mTestActivity, mDecorThickness);
updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
0, PRIVATE_FLAG_IS_SCREEN_DECOR);
- insets = getInsets(mTestActivity);
- assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
+ assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop());
updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness,
PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR);
- insets = getInsets(mTestActivity);
- assertEquals(mDecorThickness, insets.getSystemWindowInsetTop());
+ assertTopInsetEquals(mTestActivity, mDecorThickness);
}
@Test
@@ -183,17 +173,10 @@
WindowInsets initialInsets = getInsets(mTestActivity);
final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness);
- WindowInsets insets = getInsets(mTestActivity);
- assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness);
+ assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness);
removeWindow(decorWindow);
- insets = getInsets(mTestActivity);
- assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
- }
-
- private View createAppWindow() {
- return createWindow("appWindow", TOP, MATCH_PARENT, MATCH_PARENT, BLUE,
- FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR, 0);
+ assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop());
}
private View createDecorWindow(int gravity, int width, int height) {
@@ -249,10 +232,6 @@
waitForIdle();
}
- private WindowInsets getInsets(View v) {
- return new WindowInsets(v.getRootWindowInsets());
- }
-
private WindowInsets getInsets(Activity a) {
return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets());
}
@@ -269,11 +248,59 @@
lp.flags = (lp.flags & ~mask) | (flags & mask);
}
+ /**
+ * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed.
+ */
+ private void assertTopInsetEquals(Activity activity, int expected) throws Exception {
+ waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected);
+ assertEquals(expected, getInsets(activity).getSystemWindowInsetTop());
+ }
+
+ /**
+ * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected}
+ * waiting as needed.
+ */
+ private void assertInsetGreaterOrEqual(Activity activity, int side, int expected)
+ throws Exception {
+ waitFor(() -> {
+ final WindowInsets insets = getInsets(activity);
+ switch (side) {
+ case TOP: return insets.getSystemWindowInsetTop() >= expected;
+ case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected;
+ case LEFT: return insets.getSystemWindowInsetLeft() >= expected;
+ case RIGHT: return insets.getSystemWindowInsetRight() >= expected;
+ default: return true;
+ }
+ });
+
+ final WindowInsets insets = getInsets(activity);
+ switch (side) {
+ case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break;
+ case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break;
+ case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break;
+ case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break;
+ }
+ }
+
/** Asserts that the first entry is greater than or equal to the second entry. */
private void assertGreaterOrEqual(int first, int second) throws Exception {
Assert.assertTrue("Excepted " + first + " >= " + second, first >= second);
}
+ private void waitFor(BooleanSupplier waitCondition) {
+ int retriesLeft = 5;
+ do {
+ if (waitCondition.getAsBoolean()) {
+ break;
+ }
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ // Well I guess we are not waiting...
+ }
+ } while (retriesLeft-- > 0);
+ }
+
private void finishActivity(Activity a) {
if (a == null) {
return;
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 000cf38..7a55904 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -107,6 +108,13 @@
sWm = WindowManagerService.main(context, ims, true, false,
false, new TestWindowManagerPolicy());
+
+ sWm.onInitReady();
+
+ // Display creation is driven by the ActivityManagerService via ActivityStackSupervisor.
+ // We emulate those steps here.
+ sWm.mRoot.createDisplayContent(sWm.mDisplayManager.getDisplay(DEFAULT_DISPLAY),
+ mock(DisplayWindowController.class));
}
return sWm;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 6a4710b..74c72bf 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -36,10 +36,15 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
/**
* Tests for the {@link WindowState} class.
@@ -57,15 +62,17 @@
final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
- assertFalse(parentWindow.mHidden);
+ // parentWindow is initially set to hidden.
+ assertTrue(parentWindow.mHidden);
+ assertFalse(parentWindow.isParentWindowHidden());
+ assertTrue(child1.isParentWindowHidden());
+ assertTrue(child2.isParentWindowHidden());
+
+ parentWindow.mHidden = false;
assertFalse(parentWindow.isParentWindowHidden());
assertFalse(child1.isParentWindowHidden());
assertFalse(child2.isParentWindowHidden());
- parentWindow.mHidden = true;
- assertFalse(parentWindow.isParentWindowHidden());
- assertTrue(child1.isParentWindowHidden());
- assertTrue(child2.isParentWindowHidden());
}
@Test
@@ -237,11 +244,11 @@
}
private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) {
+ reset(mPowerManagerWrapper);
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
- root.mTurnOnScreen = false;
root.prepareWindowToDisplayDuringRelayout(wasVisible /*wasVisible*/);
- assertTrue(root.mTurnOnScreen);
+ verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 69b1378..91d5ea4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -29,6 +29,7 @@
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import android.content.Context;
@@ -84,6 +85,9 @@
WindowState mChildAppWindowBelow;
HashSet<WindowState> mCommonWindows;
+ @Mock
+ static WindowState.PowerManagerWrapper mPowerManagerWrapper;
+
@Before
public void setUp() throws Exception {
if (!sOneTimeSetupDone) {
@@ -245,7 +249,7 @@
attrs.setTitle(name);
final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
- 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow);
+ 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow, mPowerManagerWrapper);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
token.addWindow(w);
@@ -284,7 +288,8 @@
final int displayId = sNextDisplayId++;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- return new DisplayContent(display, sWm, new WallpaperController(sWm));
+ return new DisplayContent(display, sWm, new WallpaperController(sWm),
+ mock(DisplayWindowController.class));
}
/** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
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 cd32425..7b2c040 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -49,8 +50,10 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
+import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
@@ -64,9 +67,11 @@
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings.Secure;
@@ -149,6 +154,10 @@
@Mock private ICompanionDeviceManager mCompanionMgr;
@Mock SnoozeHelper mSnoozeHelper;
@Mock GroupHelper mGroupHelper;
+ @Mock
+ IBinder mPermOwner;
+ @Mock
+ IActivityManager mAm;
// Use a Testable subclass so we can simulate calls from the system without failing.
private static class TestableNotificationManagerService extends NotificationManagerService {
@@ -179,6 +188,11 @@
protected void reportSeen(NotificationRecord r) {
return;
}
+
+ @Override
+ protected void reportUserInteraction(NotificationRecord r) {
+ return;
+ }
}
@Before
@@ -208,6 +222,7 @@
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
+ when(mAm.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
// write to a test file; the system file isn't readable from tests
mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -238,7 +253,7 @@
mPackageManager, mPackageManagerClient, mockLightsManager,
mListeners, mAssistants, mConditionProviders,
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
- mGroupHelper);
+ mGroupHelper, mAm);
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
@@ -592,6 +607,8 @@
mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(
+ any(), any(), anyInt(), anyInt());
}
@Test
@@ -610,6 +627,7 @@
ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture());
assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -624,6 +642,7 @@
mBinderService.getActiveNotifications(sbn.getPackageName());
assertEquals(0, notifs.length);
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -637,6 +656,7 @@
mBinderService.getActiveNotifications(sbn.getPackageName());
assertEquals(0, notifs.length);
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -658,6 +678,7 @@
ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture());
assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -676,6 +697,7 @@
mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId());
waitForIdle();
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -689,6 +711,7 @@
waitForIdle();
assertEquals(0, mService.getNotificationRecordCount());
+ verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt());
}
@Test
@@ -2447,4 +2470,64 @@
mBinderService.getBackupPayload(1);
assertEquals(1, mService.countSystemChecks - systemChecks);
}
+
+ @Test
+ public void revokeUriPermissions_update() throws Exception {
+ NotificationChannel c = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ Message message1 = new Message("", 0, "");
+ message1.setData("", Uri.fromParts("old", "", "old stuff"));
+ Message message2 = new Message("", 1, "");
+ message2.setData("", Uri.fromParts("new", "", "new stuff"));
+
+ Notification.Builder nb = new Notification.Builder(mContext, c.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(message1)
+ .addMessage(message2));
+ StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord oldRecord =
+ new NotificationRecord(mContext, oldSbn, c);
+
+ Notification.Builder nb1 = new Notification.Builder(mContext, c.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MessagingStyle("").addMessage(message2));
+ StatusBarNotification newSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb1.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord newRecord =
+ new NotificationRecord(mContext, newSbn, c);
+
+ mService.revokeUriPermissions(newRecord, oldRecord);
+
+ verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()),
+ anyInt(), anyInt());
+ }
+
+ @Test
+ public void revokeUriPermissions_cancel() throws Exception {
+ NotificationChannel c = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ Message message1 = new Message("", 0, "");
+ message1.setData("", Uri.fromParts("old", "", "old stuff"));
+
+ Notification.Builder nb = new Notification.Builder(mContext, c.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(message1));
+ StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord oldRecord =
+ new NotificationRecord(mContext, oldSbn, c);
+
+ mService.revokeUriPermissions(null, oldRecord);
+
+ verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()),
+ anyInt(), anyInt());
+ }
}
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index cc21199..32db752 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -177,6 +177,12 @@
long mAppIdleParoleDurationMillis;
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
+ /** Minimum time a strong usage event should keep the bucket elevated. */
+ long mStrongUsageTimeoutMillis;
+ /** Minimum time a notification seen event should keep the bucket elevated. */
+ long mNotificationSeenTimeoutMillis;
+ /** Minimum time a system update event should keep the buckets elevated. */
+ long mSystemUpdateUsageTimeoutMillis;
volatile boolean mAppIdleEnabled;
boolean mAppIdleTempParoled;
@@ -330,7 +336,7 @@
synchronized (mAppIdleLock) {
AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId,
STANDBY_BUCKET_ACTIVE, elapsedRealtime,
- elapsedRealtime + 2 * ONE_HOUR);
+ elapsedRealtime + mStrongUsageTimeoutMillis);
maybeInformListeners(packageName, userId, elapsedRealtime,
appUsage.currentBucket, false);
}
@@ -539,6 +545,7 @@
}
}
+ @GuardedBy("mAppIdleLock")
@StandbyBuckets int getBucketForLocked(String packageName, int userId,
long elapsedRealtime) {
int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
@@ -627,11 +634,11 @@
if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_WORKING_SET,
- elapsedRealtime, elapsedRealtime + 2 * ONE_HOUR);
+ elapsedRealtime, elapsedRealtime + mNotificationSeenTimeoutMillis);
} else {
mAppIdleHistory.reportUsage(event.mPackage, userId,
STANDBY_BUCKET_ACTIVE,
- elapsedRealtime, elapsedRealtime + 2 * ONE_HOUR);
+ elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
}
final boolean userStartedInteracting =
@@ -1113,10 +1120,10 @@
final PackageInfo pi = packages.get(i);
String packageName = pi.packageName;
if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
- // Mark app as used for 4 hours. After that it can timeout to whatever the
+ // Mark app as used for 2 hours. After that it can timeout to whatever the
// past usage pattern was.
mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE, 0,
- elapsedRealtime + 4 * ONE_HOUR);
+ elapsedRealtime + mSystemUpdateUsageTimeoutMillis);
}
}
}
@@ -1395,6 +1402,12 @@
private static final String KEY_PAROLE_DURATION = "parole_duration";
private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
+ private static final String KEY_STRONG_USAGE_HOLD_DURATION = "strong_usage_duration";
+ private static final String KEY_NOTIFICATION_SEEN_HOLD_DURATION =
+ "notification_seen_duration";
+ private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION =
+ "system_update_usage_duration";
+
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -1455,7 +1468,15 @@
ELAPSED_TIME_THRESHOLDS);
mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4,
COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
-
+ mStrongUsageTimeoutMillis = mParser.getDurationMillis
+ (KEY_STRONG_USAGE_HOLD_DURATION,
+ COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR);
+ mNotificationSeenTimeoutMillis = mParser.getDurationMillis
+ (KEY_NOTIFICATION_SEEN_HOLD_DURATION,
+ COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR);
+ mSystemUpdateUsageTimeoutMillis = mParser.getDurationMillis
+ (KEY_SYSTEM_UPDATE_HOLD_DURATION,
+ COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR);
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 36a2a95..3b0fd1f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -654,7 +654,7 @@
public boolean isAppInactive(String packageName, int userId) {
try {
userId = ActivityManager.getService().handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, "isAppInactive", null);
+ Binder.getCallingUid(), userId, false, false, "isAppInactive", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 4b2d9b9..43f189b 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -314,6 +314,7 @@
* Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
* Should only by called by owner.
*/
+ @GuardedBy("mLock")
private void upgradeSingleUserLocked() {
if (sSingleUserSettingsFile.exists()) {
mDevicePreferenceMap.clear();
@@ -347,6 +348,7 @@
}
}
+ @GuardedBy("mLock")
private void readSettingsLocked() {
if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
@@ -386,6 +388,7 @@
* <p>In the uncommon case that the system crashes in between the scheduling and the write the
* update is lost.</p>
*/
+ @GuardedBy("mLock")
private void scheduleWriteSettingsLocked() {
if (mIsWriteSettingsScheduled) {
return;
@@ -869,6 +872,7 @@
return null;
}
+ @GuardedBy("mLock")
private boolean clearCompatibleMatchesLocked(@NonNull UserPackage userPackage,
@NonNull DeviceFilter filter) {
ArrayList<DeviceFilter> keysToRemove = new ArrayList<>();
@@ -892,6 +896,7 @@
return !keysToRemove.isEmpty();
}
+ @GuardedBy("mLock")
private boolean clearCompatibleMatchesLocked(@NonNull UserPackage userPackage,
@NonNull AccessoryFilter filter) {
ArrayList<AccessoryFilter> keysToRemove = new ArrayList<>();
@@ -915,6 +920,7 @@
return !keysToRemove.isEmpty();
}
+ @GuardedBy("mLock")
private boolean handlePackageAddedLocked(UserPackage userPackage, ActivityInfo aInfo,
String metaDataName) {
XmlResourceParser parser = null;
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 1edc469..c1a7591 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -236,6 +236,7 @@
*
* @return Iff the caller is in the current user's profile group
*/
+ @GuardedBy("mLock")
private boolean isCallerInCurrentUserProfileGroupLocked() {
int userIdInt = UserHandle.getCallingUserId();
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 6799417..8c18518 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -352,8 +352,11 @@
*/
public static final int CAPABILITY_CAN_PULL_CALL = 0x00800000;
+ /** Call supports the deflect feature. */
+ public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
+
//******************************************************************************************
- // Next CAPABILITY value: 0x01000000
+ // Next CAPABILITY value: 0x02000000
//******************************************************************************************
/**
@@ -528,6 +531,9 @@
if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
builder.append(" CAPABILITY_CAN_PULL_CALL");
}
+ if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
+ builder.append(" CAPABILITY_SUPPORT_DEFLECT");
+ }
builder.append("]");
return builder.toString();
}
@@ -1235,6 +1241,15 @@
}
/**
+ * Instructs this {@link #STATE_RINGING} {@code Call} to deflect.
+ *
+ * @param address The address to which the call will be deflected.
+ */
+ public void deflect(Uri address) {
+ mInCallAdapter.deflectCall(mTelecomCallId, address);
+ }
+
+ /**
* Instructs this {@link #STATE_RINGING} {@code Call} to reject.
*
* @param rejectWithMessage Whether to reject with a text message.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index e0b0bbf..24184e0 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -328,8 +328,11 @@
*/
public static final int CAPABILITY_CAN_PULL_CALL = 0x01000000;
+ /** Call supports the deflect feature. */
+ public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
+
//**********************************************************************************************
- // Next CAPABILITY value: 0x02000000
+ // Next CAPABILITY value: 0x04000000
//**********************************************************************************************
/**
@@ -726,6 +729,9 @@
if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
builder.append(isLong ? " CAPABILITY_CAN_PULL_CALL" : " pull");
}
+ if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
+ builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
+ }
builder.append("]");
return builder.toString();
@@ -2752,6 +2758,12 @@
/**
* Notifies this Connection, which is in {@link #STATE_RINGING}, of
+ * a request to deflect.
+ */
+ public void onDeflect(Uri address) {}
+
+ /**
+ * Notifies this Connection, which is in {@link #STATE_RINGING}, of
* a request to reject.
*/
public void onReject() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index c1040ad..211699e 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -124,6 +124,7 @@
private static final String SESSION_ABORT = "CS.ab";
private static final String SESSION_ANSWER = "CS.an";
private static final String SESSION_ANSWER_VIDEO = "CS.anV";
+ private static final String SESSION_DEFLECT = "CS.def";
private static final String SESSION_REJECT = "CS.r";
private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
private static final String SESSION_SILENCE = "CS.s";
@@ -181,6 +182,7 @@
private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
private static final int MSG_HANDOVER_FAILED = 32;
private static final int MSG_HANDOVER_COMPLETE = 33;
+ private static final int MSG_DEFLECT = 34;
private static Connection sNullConnection;
@@ -353,6 +355,20 @@
}
@Override
+ public void deflect(String callId, Uri address, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_DEFLECT);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = address;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_DEFLECT, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void reject(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_REJECT);
try {
@@ -847,6 +863,17 @@
}
break;
}
+ case MSG_DEFLECT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_DEFLECT);
+ try {
+ deflect((String) args.arg1, (Uri) args.arg2);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
case MSG_REJECT: {
SomeArgs args = (SomeArgs) msg.obj;
Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
@@ -1605,6 +1632,11 @@
findConnectionForAction(callId, "answer").onAnswer();
}
+ private void deflect(String callId, Uri address) {
+ Log.d(this, "deflect %s", callId);
+ findConnectionForAction(callId, "deflect").onDeflect(address);
+ }
+
private void reject(String callId) {
Log.d(this, "reject %s", callId);
findConnectionForAction(callId, "reject").onReject();
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index dcf5c27..1de67a5 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -33,47 +33,48 @@
public final class DisconnectCause implements Parcelable {
/** Disconnected because of an unknown or unspecified reason. */
- public static final int UNKNOWN = 0;
+ public static final int UNKNOWN = TelecomProtoEnums.UNKNOWN; // = 0
/** Disconnected because there was an error, such as a problem with the network. */
- public static final int ERROR = 1;
+ public static final int ERROR = TelecomProtoEnums.ERROR; // = 1
/** Disconnected because of a local user-initiated action, such as hanging up. */
- public static final int LOCAL = 2;
+ public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2
/**
* Disconnected because of a remote user-initiated action, such as the other party hanging up
* up.
*/
- public static final int REMOTE = 3;
+ public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3
/** Disconnected because it has been canceled. */
- public static final int CANCELED = 4;
+ public static final int CANCELED = TelecomProtoEnums.CANCELED; // = 4
/** Disconnected because there was no response to an incoming call. */
- public static final int MISSED = 5;
+ public static final int MISSED = TelecomProtoEnums.MISSED; // = 5
/** Disconnected because the user rejected an incoming call. */
- public static final int REJECTED = 6;
+ public static final int REJECTED = TelecomProtoEnums.REJECTED; // = 6
/** Disconnected because the other party was busy. */
- public static final int BUSY = 7;
+ public static final int BUSY = TelecomProtoEnums.BUSY; // = 7
/**
* Disconnected because of a restriction on placing the call, such as dialing in airplane
* mode.
*/
- public static final int RESTRICTED = 8;
+ public static final int RESTRICTED = TelecomProtoEnums.RESTRICTED; // = 8
/** Disconnected for reason not described by other disconnect codes. */
- public static final int OTHER = 9;
+ public static final int OTHER = TelecomProtoEnums.OTHER; // = 9
/**
* Disconnected because the connection manager did not support the call. The call will be tried
* again without a connection manager. See {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
*/
- public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10;
+ public static final int CONNECTION_MANAGER_NOT_SUPPORTED =
+ TelecomProtoEnums.CONNECTION_MANAGER_NOT_SUPPORTED; // = 10
/**
* Disconnected because the user did not locally answer the incoming call, but it was answered
* on another device where the call was ringing.
*/
- public static final int ANSWERED_ELSEWHERE = 11;
+ public static final int ANSWERED_ELSEWHERE = TelecomProtoEnums.ANSWERED_ELSEWHERE; // = 11
/**
* Disconnected because the call was pulled from the current device to another device.
*/
- public static final int CALL_PULLED = 12;
+ public static final int CALL_PULLED = TelecomProtoEnums.CALL_PULLED; // = 12
/**
* Reason code (returned via {@link #getReason()}) which indicates that a call could not be
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 658685f..8678e33 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.net.Uri;
import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.os.RemoteException;
@@ -61,6 +62,19 @@
}
/**
+ * Instructs Telecom to deflect the specified call.
+ *
+ * @param callId The identifier of the call to deflect.
+ * @param address The address to deflect.
+ */
+ public void deflectCall(String callId, Uri address) {
+ try {
+ mAdapter.deflectCall(callId, address);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Instructs Telecom to reject the specified call.
*
* @param callId The identifier of the call to reject.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 3a84f00..e35093c 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telecom;
+import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
@@ -58,6 +59,8 @@
void answer(String callId, in Session.Info sessionInfo);
+ void deflect(String callId, in Uri address, in Session.Info sessionInfo);
+
void reject(String callId, in Session.Info sessionInfo);
void rejectWithMessage(String callId, String message, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 87ccd3e..57df5c1 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telecom;
+import android.net.Uri;
import android.os.Bundle;
import android.telecom.PhoneAccountHandle;
@@ -29,6 +30,8 @@
oneway interface IInCallAdapter {
void answerCall(String callId, int videoState);
+ void deflectCall(String callId, in Uri address);
+
void rejectCall(String callId, boolean rejectWithMessage, String textMessage);
void disconnectCall(String callId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7eb691b..c8c898a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1376,6 +1376,12 @@
*/
public static final String KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL = "allow_hold_in_ims_call";
+ /**
+ * Flag indicating whether the carrier supports call deflection for an incoming IMS call.
+ * @hide
+ */
+ public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL =
+ "carrier_allow_deflect_ims_call_bool";
/**
* Flag indicating whether the carrier always wants to play an "on-hold" tone when a call has
@@ -1844,6 +1850,7 @@
static {
sDefaults = new PersistableBundle();
sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false);
sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false);
diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java
deleted file mode 100644
index b362df9..0000000
--- a/telephony/java/android/telephony/LocationAccessPolicy.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.location.LocationManager;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Process;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.SparseBooleanArray;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Helper for performing location access checks.
- * @hide
- */
-public final class LocationAccessPolicy {
- /**
- * API to determine if the caller has permissions to get cell location.
- *
- * @param pkgName Package name of the application requesting access
- * @param uid The uid of the package
- * @param pid The pid of the package
- * @return boolean true or false if permissions is granted
- */
- public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName,
- int uid, int pid) throws SecurityException {
- Trace.beginSection("TelephonyLocationCheck");
- try {
- // Always allow the phone process to access location. This avoid breaking legacy code
- // that rely on public-facing APIs to access cell location, and it doesn't create a
- // info leak risk because the cell location is stored in the phone process anyway.
- if (uid == Process.PHONE_UID) {
- return true;
- }
-
- // We always require the location permission and also require the
- // location mode to be on for non-legacy apps. Legacy apps are
- // required to be in the foreground to at least mitigate the case
- // where a legacy app the user is not using tracks their location.
- // Granting ACCESS_FINE_LOCATION to an app automatically grants it
- // ACCESS_COARSE_LOCATION.
-
- if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) ==
- PackageManager.PERMISSION_DENIED) {
- return false;
- }
- final int opCode = AppOpsManager.permissionToOpCode(
- Manifest.permission.ACCESS_COARSE_LOCATION);
- if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class)
- .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
- if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))
- && !isLegacyForeground(context, pkgName, uid)) {
- return false;
- }
- // If the user or profile is current, permission is granted.
- // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
- return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context);
- } finally {
- Trace.endSection();
- }
- }
-
- private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {
- int locationMode = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId);
- return locationMode != Settings.Secure.LOCATION_MODE_OFF
- && locationMode != Settings.Secure.LOCATION_MODE_SENSORS_ONLY;
- }
-
- private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName,
- int uid) {
- long token = Binder.clearCallingIdentity();
- try {
- return isLegacyVersion(context, pkgName) && isForegroundApp(context, uid);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) {
- try {
- if (context.getPackageManager().getApplicationInfo(pkgName, 0)
- .targetSdkVersion <= Build.VERSION_CODES.O) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- // In case of exception, assume known app (more strict checking)
- // Note: This case will never happen since checkPackage is
- // called to verify validity before checking app's version.
- }
- return false;
- }
-
- private static boolean isForegroundApp(@NonNull Context context, int uid) {
- final ActivityManager am = context.getSystemService(ActivityManager.class);
- return am.getUidImportance(uid) <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
- }
-
- private static boolean checkInteractAcrossUsersFull(@NonNull Context context) {
- return context.checkCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private static boolean isCurrentProfile(@NonNull Context context, int uid) {
- long token = Binder.clearCallingIdentity();
- try {
- final int currentUser = ActivityManager.getCurrentUser();
- final int callingUserId = UserHandle.getUserId(uid);
- if (callingUserId == currentUser) {
- return true;
- } else {
- List<UserInfo> userProfiles = context.getSystemService(
- UserManager.class).getProfiles(currentUser);
- for (UserInfo user : userProfiles) {
- if (user.id == callingUserId) {
- return true;
- }
- }
- }
- return false;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 34f2dac..debf43d 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -612,9 +612,9 @@
* onSubscriptionsChanged overridden.
*/
public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
- String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
if (DBG) {
- logd("register OnSubscriptionsChangedListener pkgName=" + pkgName
+ logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug
+ " listener=" + listener);
}
try {
@@ -623,7 +623,7 @@
ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
if (tr != null) {
- tr.addOnSubscriptionsChangedListener(pkgName, listener.callback);
+ tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
}
} catch (RemoteException ex) {
// Should not happen
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5c290da..03a8f33 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6587,11 +6587,48 @@
* @hide
*/
public void setBasebandVersionForPhone(int phoneId, String version) {
+ setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_BASEBAND_VERSION, version);
+ }
+
+ /**
+ * Get baseband version for the default phone.
+ *
+ * @return baseband version.
+ * @hide
+ */
+ public String getBasebandVersion() {
+ int phoneId = getPhoneId();
+ return getBasebandVersionForPhone(phoneId);
+ }
+
+ /**
+ * Get baseband version for the default phone using the legacy approach.
+ * This change was added in P, to ensure backward compatiblity.
+ *
+ * @return baseband version.
+ * @hide
+ */
+ private String getBasebandVersionLegacy(int phoneId) {
if (SubscriptionManager.isValidPhoneId(phoneId)) {
String prop = TelephonyProperties.PROPERTY_BASEBAND_VERSION +
((phoneId == 0) ? "" : Integer.toString(phoneId));
- SystemProperties.set(prop, version);
+ return SystemProperties.get(prop);
}
+ return null;
+ }
+
+ /**
+ * Get baseband version by phone id.
+ *
+ * @return baseband version.
+ * @hide
+ */
+ public String getBasebandVersionForPhone(int phoneId) {
+ String version = getBasebandVersionLegacy(phoneId);
+ if (version != null && !version.isEmpty()) {
+ setBasebandVersionForPhone(phoneId, version);
+ }
+ return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_BASEBAND_VERSION, "");
}
/**
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 0b3cbad..0c17147 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -55,10 +55,11 @@
/** Card state restricted. */
public static final int CARD_STATE_INFO_RESTRICTED = 4;
- public final boolean isActive;
- public final boolean isEuicc;
- public final String cardId;
- public final @CardStateInfo int cardStateInfo;
+ private final boolean mIsActive;
+ private final boolean mIsEuicc;
+ private final String mCardId;
+ private final @CardStateInfo int mCardStateInfo;
+ private final int mLogicalSlotIdx;
public static final Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() {
@Override
@@ -73,18 +74,20 @@
};
private UiccSlotInfo(Parcel in) {
- isActive = in.readByte() != 0;
- isEuicc = in.readByte() != 0;
- cardId = in.readString();
- cardStateInfo = in.readInt();
+ mIsActive = in.readByte() != 0;
+ mIsEuicc = in.readByte() != 0;
+ mCardId = in.readString();
+ mCardStateInfo = in.readInt();
+ mLogicalSlotIdx = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeByte((byte) (isActive ? 1 : 0));
- dest.writeByte((byte) (isEuicc ? 1 : 0));
- dest.writeString(cardId);
- dest.writeInt(cardStateInfo);
+ dest.writeByte((byte) (mIsActive ? 1 : 0));
+ dest.writeByte((byte) (mIsEuicc ? 1 : 0));
+ dest.writeString(mCardId);
+ dest.writeInt(mCardStateInfo);
+ dest.writeInt(mLogicalSlotIdx);
}
@Override
@@ -93,28 +96,33 @@
}
public UiccSlotInfo(boolean isActive, boolean isEuicc, String cardId,
- @CardStateInfo int cardStateInfo) {
- this.isActive = isActive;
- this.isEuicc = isEuicc;
- this.cardId = cardId;
- this.cardStateInfo = cardStateInfo;
+ @CardStateInfo int cardStateInfo, int logicalSlotIdx) {
+ this.mIsActive = isActive;
+ this.mIsEuicc = isEuicc;
+ this.mCardId = cardId;
+ this.mCardStateInfo = cardStateInfo;
+ this.mLogicalSlotIdx = logicalSlotIdx;
}
public boolean getIsActive() {
- return isActive;
+ return mIsActive;
}
public boolean getIsEuicc() {
- return isEuicc;
+ return mIsEuicc;
}
public String getCardId() {
- return cardId;
+ return mCardId;
}
@CardStateInfo
public int getCardStateInfo() {
- return cardStateInfo;
+ return mCardStateInfo;
+ }
+
+ public int getLogicalSlotIdx() {
+ return mLogicalSlotIdx;
}
@Override
@@ -127,32 +135,36 @@
}
UiccSlotInfo that = (UiccSlotInfo) obj;
- return (isActive == that.isActive)
- && (isEuicc == that.isEuicc)
- && (cardId == that.cardId)
- && (cardStateInfo == that.cardStateInfo);
+ return (mIsActive == that.mIsActive)
+ && (mIsEuicc == that.mIsEuicc)
+ && (mCardId == that.mCardId)
+ && (mCardStateInfo == that.mCardStateInfo)
+ && (mLogicalSlotIdx == that.mLogicalSlotIdx);
}
@Override
public int hashCode() {
int result = 1;
- result = 31 * result + (isActive ? 1 : 0);
- result = 31 * result + (isEuicc ? 1 : 0);
- result = 31 * result + Objects.hashCode(cardId);
- result = 31 * result + cardStateInfo;
+ result = 31 * result + (mIsActive ? 1 : 0);
+ result = 31 * result + (mIsEuicc ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mCardId);
+ result = 31 * result + mCardStateInfo;
+ result = 31 * result + mLogicalSlotIdx;
return result;
}
@Override
public String toString() {
- return "UiccSlotInfo (isActive="
- + isActive
- + ", isEuicc="
- + isEuicc
- + ", cardId="
- + cardId
+ return "UiccSlotInfo (mIsActive="
+ + mIsActive
+ + ", mIsEuicc="
+ + mIsEuicc
+ + ", mCardId="
+ + mCardId
+ ", cardState="
- + cardStateInfo
+ + mCardStateInfo
+ + ", phoneId="
+ + mLogicalSlotIdx
+ ")";
}
}
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index c3d103f..a20d4f5 100644
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -754,6 +754,22 @@
}
/**
+ * Deflects an incoming call.
+ *
+ * @param number number to be deflected to
+ */
+ public void deflect(String number) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.deflect(number);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
index 00cb1e2..e5ed825 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -182,6 +182,15 @@
}
/**
+ * Deflects an incoming call.
+ *
+ * @param deflectNumber number to deflect the call
+ */
+ @Override
+ public void deflect(String deflectNumber) {
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index c6ca6fd..7b9fe2b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -174,6 +174,11 @@
}
@Override
+ public void deflect(String deflectNumber) {
+ ImsCallSessionImplBase.this.deflect(deflectNumber);
+ }
+
+ @Override
public void reject(int reason) {
ImsCallSessionImplBase.this.reject(reason);
}
@@ -395,6 +400,14 @@
}
/**
+ * Deflects an incoming call.
+ *
+ * @param deflectNumber number to deflect the call
+ */
+ public void deflect(String deflectNumber) {
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index 203e6cf..15234e5 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -136,6 +136,13 @@
void accept(int callType, in ImsStreamMediaProfile profile);
/**
+ * Deflects an incoming call.
+ *
+ * @param deflectNumber number to deflect the call
+ */
+ void deflect(String deflectNumber);
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 62fed61..b65cda9 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -24,6 +24,9 @@
srcs: ["src/**/*.java"],
+ // Needs to be consistent with the repackaged version of this make target.
+ java_version: "1.8",
+
no_framework_libs: true,
hostdex: true,
libs: [
@@ -55,21 +58,22 @@
name: "repackaged.android.test.base",
static_libs: ["android.test.base"],
-
no_framework_libs: true,
libs: [
"framework",
],
jarjar_rules: "jarjar-rules.txt",
+ // Pin java_version until jarjar is certified to support later versions. http://b/72703434
+ java_version: "1.8",
}
// Build the android.test.base-minus-junit library
// ===============================================
// This contains the android.test classes from android.test.base plus
// the com.android.internal.util.Predicate[s] classes. This is only
-// intended for inclusion in the android.test.legacy static library and
-// must not be used elsewhere.
+// intended for inclusion in the android.test.legacy and
+// legacy-android-test static libraries and must not be used elsewhere.
java_library_static {
name: "android.test.base-minus-junit",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index b1ae40e..54e07a16 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -19,6 +19,8 @@
java_library {
name: "android.test.mock",
+ // Needs to be consistent with the repackaged version of this make target.
+ java_version: "1.8",
srcs: ["src/**/*.java"],
no_framework_libs: true,
@@ -35,4 +37,6 @@
static_libs: ["android.test.mock"],
jarjar_rules: "jarjar-rules.txt",
+ // Pin java_version until jarjar is certified to support later versions. http://b/72703434
+ java_version: "1.8",
}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index dfaeed5..66b9527 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -19,6 +19,8 @@
java_library {
name: "android.test.runner",
+ // Needs to be consistent with the repackaged version of this make target.
+ java_version: "1.8",
srcs: ["src/**/*.java"],
no_framework_libs: true,
@@ -31,7 +33,8 @@
// Build the android.test.runner-minus-junit library
// =================================================
-// This is provided solely for use by the legacy-android-test module.
+// This is only intended for inclusion in the android.test.legacy and
+// legacy-android-test static libraries and must not be used elsewhere.
java_library {
name: "android.test.runner-minus-junit",
@@ -54,4 +57,6 @@
static_libs: ["android.test.runner"],
jarjar_rules: "jarjar-rules.txt",
+ // Pin java_version until jarjar is certified to support later versions. http://b/72703434
+ java_version: "1.8",
}
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
index 7ea9ba3..1f06121 100644
--- a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
@@ -17,16 +17,19 @@
package com.android.frameworks.perftests.amteststestapp;
import android.app.Activity;
-import android.os.Bundle;
+import android.os.Looper;
+import android.os.MessageQueue;
import com.android.frameworks.perftests.am.util.Constants;
import com.android.frameworks.perftests.am.util.Utils;
public class TestActivity extends Activity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Utils.sendTime(getIntent(), Constants.TYPE_ACTIVITY_CREATED);
+ protected void onResume() {
+ super.onResume();
+ Looper.myQueue().addIdleHandler(() -> {
+ Utils.sendTime(getIntent(), Constants.TYPE_TARGET_PACKAGE_START);
+ return false;
+ });
}
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
index c867141..26a8e7b 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -62,7 +62,7 @@
sleep();
}
// make sure Application has run
- timeReceiver.getReceivedTimeNs(Constants.TYPE_ACTIVITY_CREATED);
+ timeReceiver.getReceivedTimeNs(Constants.TYPE_TARGET_PACKAGE_START);
Utils.drainBroadcastQueue();
}
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
index 6528028..f35c2fd 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -17,7 +17,7 @@
package com.android.frameworks.perftests.am.util;
public class Constants {
- public static final String TYPE_ACTIVITY_CREATED = "activity_create";
+ public static final String TYPE_TARGET_PACKAGE_START = "target_package_start";
public static final String TYPE_BROADCAST_RECEIVE = "broadcast_receive";
public static final String ACTION_BROADCAST_MANIFEST_RECEIVE =
diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml
index 2591aaf..d62ef9e 100644
--- a/tests/FrameworkPerf/AndroidManifest.xml
+++ b/tests/FrameworkPerf/AndroidManifest.xml
@@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworkperf">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="5" />
diff --git a/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java b/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java
index d46fff4..a7d5ae8 100644
--- a/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java
+++ b/tests/Internal/src/com/android/internal/colorextraction/types/TonalTest.java
@@ -99,6 +99,21 @@
}
@Test
+ public void tonal_rangeTest() {
+ Tonal.ConfigParser config = new Tonal.ConfigParser(InstrumentationRegistry.getContext());
+ for (Tonal.TonalPalette palette : config.getTonalPalettes()) {
+ assertTrue("minHue should be >= to 0.", palette.minHue >= 0);
+ assertTrue("maxHue should be <= to 360.", palette.maxHue <= 360);
+
+ assertTrue("S should be >= to 0.", palette.s[0] >= 0);
+ assertTrue("S should be <= to 1.", palette.s[1] <= 1);
+
+ assertTrue("L should be >= to 0.", palette.l[0] >= 0);
+ assertTrue("L should be <= to 1.", palette.l[1] <= 1);
+ }
+ }
+
+ @Test
public void tonal_blacklistTest() {
// Make sure that palette generation will fail.
final Tonal tonal = new Tonal(InstrumentationRegistry.getContext());
diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
index 12568a0..291ba78 100644
--- a/tests/JankBench/Android.mk
+++ b/tests/JankBench/Android.mk
@@ -19,7 +19,7 @@
LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-design \
+ $(ANDROID_SUPPORT_DESIGN_TARGETS) \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-cardview \
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index c6824ec..8697f1b 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -5,6 +5,7 @@
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="19"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
diff --git a/tests/UiBench/Android.mk b/tests/UiBench/Android.mk
index 60327e5..c8e6c20 100644
--- a/tests/UiBench/Android.mk
+++ b/tests/UiBench/Android.mk
@@ -15,7 +15,7 @@
LOCAL_USE_AAPT2 := true
LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-design \
+ $(ANDROID_SUPPORT_DESIGN_TARGETS) \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-cardview \
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 9aad413..04266c5 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -172,7 +172,7 @@
final int iterations = 1000;
final String expectedAndroidOui = "da:a1:19";
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddress.createRandomUnicastAddressWithGoogleBase();
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -195,6 +195,15 @@
assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
stringRepr.startsWith(expectedLocalOui));
}
+
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddress.createRandomUnicastAddress();
+ String stringRepr = mac.toString();
+
+ assertTrue(stringRepr + " expected to be a locally assigned address",
+ mac.isLocallyAssigned());
+ assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
+ }
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6e643a3..e7abede 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -318,6 +318,7 @@
// This test has an inherent race condition in it, and cannot be enabled for continuous testing
// or presubmit tests. It is kept for manual runs and documentation purposes.
+ @Ignore
public void verifyThatNotWaitingForIdleCausesRaceConditions() {
// Bring up a network that we can use to send messages to ConnectivityService.
ConditionVariable cv = waitForConnectivityBroadcasts(1);
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 669afe1..734a5ab 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4848,6 +4848,7 @@
const String16 pathInterpolator16("pathInterpolator");
const String16 objectAnimator16("objectAnimator");
const String16 gradient16("gradient");
+ const String16 animatedSelector16("animated-selector");
const int minSdk = getMinSdkVersion(bundle);
if (minSdk >= SDK_LOLLIPOP_MR1) {
@@ -4876,7 +4877,8 @@
node->getElementName() == animatedVector16 ||
node->getElementName() == objectAnimator16 ||
node->getElementName() == pathInterpolator16 ||
- node->getElementName() == gradient16)) {
+ node->getElementName() == gradient16 ||
+ node->getElementName() == animatedSelector16)) {
// We were told not to version vector tags, so skip the children here.
continue;
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index c9e272c..15c5eae 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -450,7 +450,7 @@
static bool IsVectorElement(const std::string& name) {
return name == "vector" || name == "animated-vector" || name == "pathInterpolator" ||
- name == "objectAnimator" || name == "gradient";
+ name == "objectAnimator" || name == "gradient" || name == "animated-selector";
}
template <typename T>
@@ -1446,6 +1446,13 @@
ContainerReaderEntry* entry;
ContainerReader reader(input_stream.get());
+
+ if (reader.HadError()) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed to read file: " << reader.GetError());
+ return false;
+ }
+
while ((entry = reader.Next()) != nullptr) {
if (entry->Type() == ContainerEntryType::kResTable) {
pb::ResourceTable pb_table;
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
index f278aec..d75aea5 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.sdkparcelables
import org.objectweb.asm.ClassVisitor
@@ -25,4 +41,4 @@
super.visit(version, access, name, signature, superName, interfaces)
}
-}
\ No newline at end of file
+}
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
index 3e9d92c..22e8d78 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.sdkparcelables
import org.objectweb.asm.ClassReader
@@ -53,4 +69,4 @@
fun usage() {
System.err.println("Usage: <input jar> <output aidl>")
kotlin.system.exitProcess(1)
-}
\ No newline at end of file
+}
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
index 620f798..d6a0a45 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.sdkparcelables
/** A class that uses an ancestor map to find all classes that
diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
index edfc825..c9bcbc9 100644
--- a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
+++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.sdkparcelables
import junit.framework.TestCase.assertEquals
diff --git a/wifi/java/android/net/wifi/IRttManager.aidl b/wifi/java/android/net/wifi/IRttManager.aidl
deleted file mode 100644
index 3831809..0000000
--- a/wifi/java/android/net/wifi/IRttManager.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2008 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.net.wifi;
-import android.os.Messenger;
-import android.net.wifi.RttManager;
-
-/**
- * {@hide}
- */
-interface IRttManager
-{
- Messenger getMessenger(in IBinder binder, out int[] key);
- RttManager.RttCapabilities getRttCapabilities();
-}
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index fe63aa1..bdbc149 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -12,7 +12,7 @@
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.RangingResultCallback;
import android.net.wifi.rtt.WifiRttManager;
-import android.os.Looper;
+import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -24,6 +24,7 @@
/** @hide */
@SystemApi
+@Deprecated
@SystemService(Context.WIFI_RTT_SERVICE)
public class RttManager {
@@ -181,6 +182,7 @@
/**
* This class describe the RTT capability of the Hardware
*/
+ @Deprecated
public static class RttCapabilities implements Parcelable {
/** @deprecated It is not supported*/
@Deprecated
@@ -314,12 +316,16 @@
};
}
+ /**
+ * This method is deprecated. Please use the {@link WifiRttManager} API.
+ */
@RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
public RttCapabilities getRttCapabilities() {
return mRttCapabilities;
}
/** specifies parameters for RTT request */
+ @Deprecated
public static class RttParams {
/**
* type of destination device being ranged
@@ -502,6 +508,7 @@
}
/** pseudo-private class used to parcel arguments */
+ @Deprecated
public static class ParcelableRttParams implements Parcelable {
@NonNull
@@ -589,12 +596,14 @@
};
}
+ @Deprecated
public static class WifiInformationElement {
/** Information Element ID 0xFF means element is invalid. */
public byte id;
public byte[] data;
}
/** specifies RTT results */
+ @Deprecated
public static class RttResult {
/** mac address of the device being ranged. */
public String bssid;
@@ -746,6 +755,7 @@
/** pseudo-private class used to parcel results. */
+ @Deprecated
public static class ParcelableRttResults implements Parcelable {
public RttResult mResults[];
@@ -838,8 +848,8 @@
}
dest.writeByte(result.LCR.id);
if (result.LCR.id != (byte) 0xFF) {
- dest.writeInt((byte) result.LCR.data.length);
- dest.writeByte(result.LCR.id);
+ dest.writeByte((byte) result.LCR.data.length);
+ dest.writeByteArray(result.LCR.data);
}
dest.writeByte(result.secure ? (byte) 1 : 0);
}
@@ -911,7 +921,7 @@
};
}
-
+ @Deprecated
public static interface RttListener {
public void onSuccess(RttResult[] results);
public void onFailure(int reason, String description);
@@ -919,52 +929,10 @@
}
/**
- * A parcelable that contains rtt client information.
- *
- * @hide
- */
- public static class RttClient implements Parcelable {
- // Package name of RttClient.
- private final String mPackageName;
-
- public RttClient(String packageName) {
- mPackageName = packageName;
- }
-
- protected RttClient(Parcel in) {
- mPackageName = in.readString();
- }
-
- public static final Creator<RttManager.RttClient> CREATOR =
- new Creator<RttManager.RttClient>() {
- @Override
- public RttManager.RttClient createFromParcel(Parcel in) {
- return new RttManager.RttClient(in);
- }
-
- @Override
- public RttManager.RttClient[] newArray(int size) {
- return new RttManager.RttClient[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int i) {
- parcel.writeString(mPackageName);
- }
-
- public String getPackageName() {
- return mPackageName;
- }
- }
-
- /**
* Request to start an RTT ranging
+ * <p>
+ * This method is deprecated. Please use the
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)} API.
*
* @param params -- RTT request Parameters
* @param listener -- Call back to inform RTT result
@@ -1036,6 +1004,10 @@
}
}
+ /**
+ * This method is deprecated and performs no function. Please use the {@link WifiRttManager}
+ * API.
+ */
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void stopRanging(RttListener listener) {
Log.e(TAG, "stopRanging: unsupported operation - nop");
@@ -1050,6 +1022,7 @@
* The client can freely destroy or reuse the callback after {@link RttManager#disableResponder}
* is called.
*/
+ @Deprecated
public abstract static class ResponderCallback {
/** Callback when responder is enabled. */
public abstract void onResponderEnabled(ResponderConfig config);
@@ -1065,6 +1038,10 @@
* Note calling this method with the same callback when the responder is already enabled won't
* change the responder state, a cached {@link ResponderConfig} from the last enabling will be
* returned through the callback.
+ * <p>
+ * This method is deprecated and will throw an {@link UnsupportedOperationException}
+ * exception. Please use the {@link WifiRttManager} API to perform a Wi-Fi Aware peer-to-peer
+ * ranging.
*
* @param callback Callback for responder enabling/disabling result.
* @throws IllegalArgumentException If {@code callback} is null.
@@ -1081,6 +1058,10 @@
* <p>
* Calling this method when responder isn't enabled won't have any effect. The callback can be
* reused for enabling responder after this method is called.
+ * <p>
+ * This method is deprecated and will throw an {@link UnsupportedOperationException}
+ * exception. Please use the {@link WifiRttManager} API to perform a Wi-Fi Aware peer-to-peer
+ * ranging.
*
* @param callback The same callback used for enabling responder.
* @throws IllegalArgumentException If {@code callback} is null.
@@ -1097,6 +1078,7 @@
*
* @see ScanResult
*/
+ @Deprecated
public static class ResponderConfig implements Parcelable {
// TODO: make all fields final once we can get mac address from responder HAL APIs.
@@ -1202,7 +1184,6 @@
/** @hide */
public static final int CMD_OP_REG_BINDER = BASE + 9;
- private final Context mContext;
private final WifiRttManager mNewService;
private RttCapabilities mRttCapabilities;
@@ -1211,17 +1192,14 @@
* Applications will almost always want to use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
* the standard {@link android.content.Context#WIFI_RTT_SERVICE Context.WIFI_RTT_SERVICE}.
- * @param context the application context
- * @param service the Binder interface
- * @param looper Looper for running the callbacks.
+ * @param service the new WifiRttManager service
*
* @hide
*/
- public RttManager(Context context, IRttManager service, Looper looper) {
- mContext = context;
- mNewService = (WifiRttManager) mContext.getSystemService(Context.WIFI_RTT_RANGING_SERVICE);
+ public RttManager(Context context, WifiRttManager service) {
+ mNewService = service;
- boolean rttSupported = mContext.getPackageManager().hasSystemFeature(
+ boolean rttSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_RTT);
mRttCapabilities = new RttCapabilities();
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index c46789c..8024bf0 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -273,27 +273,6 @@
public RadioChainInfo[] radioChainInfos;
/**
- * @hide
- * Update RSSI of the scan result
- * @param previousRssi
- * @param previousSeen
- * @param maxAge
- */
- public void averageRssi(int previousRssi, long previousSeen, int maxAge) {
-
- if (seen == 0) {
- seen = System.currentTimeMillis();
- }
- long age = seen - previousSeen;
-
- if (previousSeen > 0 && age > 0 && age < maxAge/2) {
- // Average the RSSI with previously seen instances of this scan result
- double alpha = 0.5 - (double) age / (double) maxAge;
- level = (int) ((double) level * (1 - alpha) + (double) previousRssi * alpha);
- }
- }
-
- /**
* Status indicating the scan result does not correspond to a user's saved configuration
* @hide
* @removed
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 7da2656..ddcf327 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -917,6 +917,9 @@
* Does not guarantee that the returned address is valid for use.
*/
public MacAddress getRandomizedMacAddress() {
+ if (mRandomizedMacAddress == null) {
+ mRandomizedMacAddress = MacAddress.ALL_ZEROS_ADDRESS;
+ }
return mRandomizedMacAddress;
}
@@ -1617,6 +1620,7 @@
creatorUid = -1;
shared = true;
dtimInterval = 0;
+ mRandomizedMacAddress = MacAddress.ALL_ZEROS_ADDRESS;
}
/**
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 32f21b9..52b3d86 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -156,14 +156,18 @@
/**
* Add the device specified by the {@code peerMacAddress} to the list of devices with
* which to measure range.
- *
+ * <p>
* The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
* Aware device may obtain its MAC address using the {@link IdentityChangedListener}
* provided to
* {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
- *
- * * Note: in order to use this API the device must support Wi-Fi Aware
- * {@link android.net.wifi.aware}.
+ * <p>
+ * Note: in order to use this API the device must support Wi-Fi Aware
+ * {@link android.net.wifi.aware}. The peer device which is being ranged to must be
+ * configured to publish a service (with any name) with:
+ * <li>Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}.
+ * <li>Ranging enabled
+ * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}.
*
* @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
@@ -179,12 +183,16 @@
/**
* Add a device specified by a {@link PeerHandle} to the list of devices with which to
* measure range.
- *
+ * <p>
* The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
* using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
- *
+ * <p>
* Note: in order to use this API the device must support Wi-Fi Aware
- * {@link android.net.wifi.aware}.
+ * {@link android.net.wifi.aware}. The peer device which is being ranged to must be
+ * configured to publish a service (with any name) with:
+ * <li>Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}.
+ * <li>Ranging enabled
+ * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}.
*
* @param peerHandle The peer handler of the peer Wi-Fi Aware device.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index e7377c1..8a3a7f5 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -176,6 +176,8 @@
@Test
public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() {
WifiConfiguration config = new WifiConfiguration();
+ assertEquals(MacAddress.ALL_ZEROS_ADDRESS, config.getRandomizedMacAddress());
+
MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress();
MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress();
@@ -185,6 +187,8 @@
@Test
public void testSetRandomizedMacAddress_ChangesSavedAddress() {
WifiConfiguration config = new WifiConfiguration();
+ assertEquals(MacAddress.ALL_ZEROS_ADDRESS, config.getRandomizedMacAddress());
+
MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
config.setRandomizedMacAddress(macToChangeInto);
MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 71ca068..f3a78bd 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -611,6 +611,7 @@
/**
* Verify the watchLocalOnlyHotspot call goes to WifiServiceImpl.
*/
+ @Test
public void testWatchLocalOnlyHotspot() throws Exception {
TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver();