diff options
468 files changed, 14668 insertions, 6924 deletions
diff --git a/Android.bp b/Android.bp index d5e04f9f4e2d..c5d64fb99f8b 100644 --- a/Android.bp +++ b/Android.bp @@ -373,7 +373,6 @@ java_library { "core/java/com/android/internal/appwidget/IAppWidgetHost.aidl", "core/java/com/android/internal/backup/IBackupTransport.aidl", "core/java/com/android/internal/backup/IObbBackupService.aidl", - "core/java/com/android/internal/car/ICarServiceHelper.aidl", "core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl", "core/java/com/android/internal/net/INetworkWatchlistManager.aidl", "core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl", @@ -579,7 +578,6 @@ java_library { "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", diff --git a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java new file mode 100644 index 000000000000..f8fd51d7b0b6 --- /dev/null +++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.app; + +import android.content.Context; +import android.content.Intent; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.perftests.utils.StubActivity; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +// Due to b/71353150, you might get "java.lang.AssertionError: Binder ProxyMap has too many +// entries", but it's flaky. Adding "Runtime.getRuntime().gc()" between each iteration solves +// the problem, but it doesn't seem like it's currently needed. + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PendingIntentPerfTest { + + private Context mContext; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private Intent mIntent; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mIntent = StubActivity.createLaunchIntent(mContext); + } + + /** + * Benchmark time to create a PendingIntent. + */ + @Test + public void create() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + 0); + + state.pauseTiming(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to create a PendingIntent with FLAG_CANCEL_CURRENT, already having an active + * PendingIntent. + */ + @Test + public void createWithCancelFlag() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + PendingIntent.FLAG_CANCEL_CURRENT); + + state.pauseTiming(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to create a PendingIntent with FLAG_UPDATE_CURRENT, already having an active + * PendingIntent. + */ + @Test + public void createWithUpdateFlag() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + state.pauseTiming(); + previousPendingIntent.cancel(); + pendingIntent.cancel(); + state.resumeTiming(); + } + } + + /** + * Benchmark time to cancel a PendingIntent. + */ + @Test + public void cancel() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, + mIntent, 0); + state.resumeTiming(); + + pendingIntent.cancel(); + } + } +} + diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutMultithreadPerfTest.java new file mode 100644 index 000000000000..60c6d89b39ce --- /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 6012f4b12b3d..8f03f7eea584 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java @@ -17,6 +17,14 @@ package android.perftests.utils; import android.app.Activity; +import android.content.Context; +import android.content.Intent; public class StubActivity extends Activity { -}
\ No newline at end of file + public static Intent createLaunchIntent(Context context) { + final Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClass(context, StubActivity.class); + return intent; + } +} diff --git a/api/current.txt b/api/current.txt index 45535c40245f..3944b45c3faa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -73,6 +73,7 @@ package android { 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"; @@ -6739,6 +6740,7 @@ package android.app.admin { 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 @@ -14603,7 +14605,7 @@ package android.graphics.drawable { method public final android.graphics.Rect copyBounds(); method public static android.graphics.drawable.Drawable createFromPath(java.lang.String); method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String); - method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options); + method public static deprecated android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options); method public static android.graphics.drawable.Drawable createFromStream(java.io.InputStream, java.lang.String); method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -40504,6 +40506,7 @@ package android.telecom { 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(); @@ -40614,6 +40617,7 @@ package android.telecom { 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 @@ -40761,6 +40765,7 @@ package android.telecom { 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(); @@ -40829,6 +40834,7 @@ package android.telecom { 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"; @@ -48146,8 +48152,6 @@ package android.view { method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect); method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect); - method protected void onDebugDraw(android.graphics.Canvas); - method protected void onDebugDrawMargins(android.graphics.Canvas, android.graphics.Paint); method public boolean onInterceptHoverEvent(android.view.MotionEvent); method public boolean onInterceptTouchEvent(android.view.MotionEvent); method protected abstract void onLayout(boolean, int, int, int, int); @@ -48221,7 +48225,6 @@ package android.view { ctor public ViewGroup.LayoutParams(android.content.Context, android.util.AttributeSet); ctor public ViewGroup.LayoutParams(int, int); ctor public ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams); - method public void onDebugDraw(android.view.View, android.graphics.Canvas, android.graphics.Paint); method public void resolveLayoutDirection(int); method protected void setBaseAttributes(android.content.res.TypedArray, int, int); field public static final deprecated int FILL_PARENT = -1; // 0xffffffff diff --git a/api/system-current.txt b/api/system-current.txt index 2d3b65a19b26..3dbb333ef789 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -30,8 +30,8 @@ package android { 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 @@ package android.app { 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 @@ package android.app.usage { 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 @@ package android.content { 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 @@ package android.content { 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"; @@ -3091,7 +3100,7 @@ package android.net.http { 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(); @@ -3167,22 +3176,22 @@ package android.net.wifi { 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); @@ -3195,7 +3204,7 @@ package android.net.wifi { 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; @@ -3210,13 +3219,13 @@ package android.net.wifi { 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; @@ -3240,7 +3249,7 @@ package android.net.wifi { 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; @@ -3277,7 +3286,7 @@ package android.net.wifi { 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; @@ -5035,22 +5044,19 @@ package android.telephony { } 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 { @@ -5711,6 +5717,7 @@ package android.telephony.ims.stub { 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 c30c0566b50d..b02da04bbcd1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1038,6 +1038,7 @@ package android.view.accessibility { 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 { @@ -1078,6 +1079,12 @@ package android.widget { field public static final int MODE_SPINNER = 1; // 0x1 } + public final class Magnifier { + method public android.graphics.Bitmap getContent(); + method public static android.graphics.PointF getMagnifierDefaultSize(); + method public android.graphics.Rect getWindowPositionOnScreen(); + } + public class NumberPicker extends android.widget.LinearLayout { method public java.lang.CharSequence getDisplayedValueForCurrentSelection(); } diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 1d5ab59f9ba8..654036ec6ab7 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -46,6 +46,7 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) { uid_t callingUid = IPCThreadState::self()->getCallingUid(); + pid_t callingPid = IPCThreadState::self()->getCallingPid(); if (callingUid == AID_ROOT || callingUid == AID_SHELL) { // root doesn't have permission.DUMP if don't do this! return Status::ok(); @@ -54,13 +55,13 @@ checkIncidentPermissions(const IncidentReportArgs& args) // checking calling permission. if (!checkCallingPermission(DUMP_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", - IPCThreadState::self()->getCallingPid(), callingUid); + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.DUMP"); } if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", - IPCThreadState::self()->getCallingPid(), callingUid); + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.USAGE_STATS"); } @@ -68,13 +69,17 @@ checkIncidentPermissions(const IncidentReportArgs& args) // checking calling request uid permission. switch (args.dest()) { case DEST_LOCAL: - if (callingUid != AID_SHELL || callingUid != AID_ROOT) { + if (callingUid != AID_SHELL && callingUid != AID_ROOT) { + ALOGW("Calling pid %d and uid %d does not have permission to get local data.", + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission to get local data."); } case DEST_EXPLICIT: - if (callingUid != AID_SHELL || callingUid != AID_ROOT || - callingUid != AID_STATSD || callingUid != AID_SYSTEM) { + if (callingUid != AID_SHELL && callingUid != AID_ROOT && + callingUid != AID_STATSD && callingUid != AID_SYSTEM) { + ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.", + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission to get explicit data."); } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index b0019ac90708..90158a00f8ec 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -44,6 +44,7 @@ statsd_common_src := \ 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 @@ statsd_common_src := \ src/storage/StorageManager.cpp \ src/StatsLogProcessor.cpp \ src/StatsService.cpp \ + src/subscriber/IncidentdReporter.cpp \ src/subscriber/SubscriberReporter.cpp \ src/HashableDimensionKey.cpp \ src/guardrail/MemoryLeakTrackUtil.cpp \ @@ -174,6 +176,7 @@ LOCAL_SRC_FILES := \ 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 f0eaeff88d34..8483b024aa55 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -181,7 +181,9 @@ bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { 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 a4066aa7bca5..7662c4025c42 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 @@ const int FIELD_ID_ID = 2; // 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 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { return; } + long curTime = time(nullptr); + if (curTime - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) { + mStatsPullerManager.ClearPullerCacheIfNecessary(curTime); + mLastPullerCacheClearTimeSec = curTime; + } + if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) { // Map the isolated uid to host uid if necessary. mapIsolatedUidToHostUidIfNecessaryLocked(event); @@ -160,7 +169,7 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { 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 @@ void StatsLogProcessor::onDumpReportLocked(const ConfigKey& key, vector<uint8_t> 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 @@ void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { StatsdStats::getInstance().noteConfigRemoved(key); mLastBroadcastTimes.erase(key); + + if (mMetricsManagers.empty()) { + mStatsPullerManager.ForceClearPullerCache(); + } } void StatsLogProcessor::flushIfNecessaryLocked( diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 17741a8da64c..8bbcd751252a 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -21,6 +21,7 @@ #include "logd/LogReader.h" #include "metrics/MetricsManager.h" #include "packages/UidMap.h" +#include "external/StatsPullerManager.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" @@ -75,6 +76,8 @@ private: sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid. + StatsPullerManager mStatsPullerManager; + sp<AnomalyMonitor> mAnomalyMonitor; void onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData); @@ -96,6 +99,8 @@ private: const long mTimeBaseSec; + long mLastPullerCacheClearTimeSec = 0; + FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 32da94f862c5..c24efec4c4fc 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 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) : 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 @@ status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& if (!args[0].compare(String8("log-app-hook"))) { return cmd_log_app_hook(out, args); } + + if (!args[0].compare(String8("clear-puller-cache"))) { + return cmd_clear_puller_cache(out); + } } print_cmd_help(out); @@ -320,6 +324,10 @@ void StatsService::print_cmd_help(FILE* out) { fprintf(out, "\n"); fprintf(out, "usage: adb shell cmd stats print-stats\n"); fprintf(out, " Prints some basic stats.\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: adb shell cmd stats clear-puller-cache\n"); + fprintf(out, " Clear cached puller data.\n"); } status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) { @@ -358,12 +366,14 @@ status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) { } 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_dump_memory_info(FILE* out) { } status_t StatsService::cmd_clear_puller_cache(FILE* out) { - mStatsPullerManager.ClearPullerCache(); - fprintf(out, "Puller cached data removed!\n"); - return NO_ERROR; + IPCThreadState* ipc = IPCThreadState::self(); + VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); + if (checkCallingPermission(String16(kPermissionDump))) { + int cleared = mStatsPullerManager.ForceClearPullerCache(); + fprintf(out, "Puller removed %d cached data!\n", cleared); + return NO_ERROR; + } else { + return PERMISSION_DENIED; + } } Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version, @@ -785,7 +801,6 @@ Status StatsService::getMetadata(vector<uint8_t>* output) { 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 @@ Status StatsService::addConfiguration(int64_t key, 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 109752b0295f..3dc19fe08d94 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -90,9 +90,19 @@ public: * 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 4912648b648d..72d29d03c863 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 @@ void AnomalyMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCo 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 @@ void AnomalyMonitor::add(sp<const AnomalyAlarm> alarm) { 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 @@ void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) { 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 @@ void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) { // 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 @@ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::popS // 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 @@ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::popS } 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::updateRegisteredAlarmTime_l(uint32_t timestampSec) { } 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 79b7d9c49f86..63f6e2acd9df 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> @@ -38,15 +36,14 @@ AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) : 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() { @@ -169,8 +166,8 @@ bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const Metric // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this. addPastBucket(key, 0, currentBucketNum - 1); } - return mAlert.has_trigger_if_sum_gt() - && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); + return mAlert.has_trigger_if_sum_gt() && + getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); } void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) { @@ -186,7 +183,7 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDim if (!mSubscriptions.empty()) { if (mAlert.has_id()) { - ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id()); + ALOGI("An anomaly (%llu) has occurred! Informing subscribers.", mAlert.id()); informSubscribers(key); } else { ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers."); @@ -231,44 +228,26 @@ void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) { return; } - std::set<int> incidentdSections; - for (const Subscription& subscription : mSubscriptions) { switch (subscription.subscriber_information_case()) { case Subscription::SubscriberInformationCase::kIncidentdDetails: - for (int i = 0; i < subscription.incidentd_details().section_size(); i++) { - incidentdSections.insert(subscription.incidentd_details().section(i)); + if (!GenerateIncidentReport(subscription.incidentd_details(), mAlert, mConfigKey)) { + ALOGW("Failed to generate incident report."); } break; case Subscription::SubscriberInformationCase::kPerfettoDetails: - CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details()); + if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) { + ALOGW("Failed to generate prefetto traces."); + } break; case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails: - SubscriberReporter::getInstance() - .alertBroadcastSubscriber(mConfigKey, subscription, key); + SubscriberReporter::getInstance().alertBroadcastSubscriber(mConfigKey, subscription, + key); break; default: break; } } - if (!incidentdSections.empty()) { - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service != NULL) { - IncidentReportArgs incidentReport; - for (const auto section : incidentdSections) { - incidentReport.addSection(section); - } - android::os::IncidentHeaderProto header; - header.set_alert_id(mAlert.id()); - header.mutable_config_key()->set_uid(mConfigKey.GetUid()); - header.mutable_config_key()->set_id(mConfigKey.GetId()); - incidentReport.addHeader(header); - service->reportIncident(incidentReport); - } else { - ALOGW("Couldn't get the incident service."); - } - } } } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index f01a97f86cf6..3be959d14109 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -16,22 +16,24 @@ #pragma once +#include <memory> // unique_ptr + +#include <stdlib.h> + #include <gtest/gtest_prod.h> +#include <utils/RefBase.h> + #include "AnomalyMonitor.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert #include "stats_util.h" // HashableDimensionKey and DimToValMap -#include <memory> // unique_ptr -#include <stdlib.h> -#include <utils/RefBase.h> - namespace android { namespace os { namespace statsd { -using std::unordered_map; using std::shared_ptr; +using std::unordered_map; // Does NOT allow negative values. class AnomalyTracker : public virtual RefBase { @@ -60,12 +62,11 @@ public: // Detects the alert and informs the incidentd when applicable. void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum, - const MetricDimensionKey& key, - const int64_t& currentBucketValue); + const MetricDimensionKey& key, const int64_t& currentBucketValue); // Init the AnomalyMonitor which is shared across anomaly trackers. virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) { - return; // Base AnomalyTracker class has no need for the AnomalyMonitor. + return; // Base AnomalyTracker class has no need for the AnomalyMonitor. } // Helper function to return the sum value of past buckets at given dimension. @@ -92,9 +93,10 @@ public: // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker, // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor. - virtual void informAlarmsFired(const uint64_t& timestampNs, + virtual void informAlarmsFired( + const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { - return; // The base AnomalyTracker class doesn't have alarms. + return; // The base AnomalyTracker class doesn't have alarms. } protected: diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp index bbee9fa5358c..fa0bc0c400b0 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::declareAnomalyIfAlarmExpired(const MetricDimensionK } void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, - const uint64_t& timestampNs) { - + const uint64_t& timestampNs) { uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); if (isInRefractoryPeriod(timestampNs, dimensionKey)) { VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period"); @@ -86,15 +85,15 @@ void DurationAnomalyTracker::stopAllAlarms() { } } -void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, +void DurationAnomalyTracker::informAlarmsFired( + const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { - if (firedAlarms.empty() || mAlarms.empty()) return; // Find the intersection of firedAlarms and mAlarms. // The for loop is inefficient, since it loops over all keys, but that's okay since it is very // seldomly called. The alternative would be having AnomalyAlarms store information about the - // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is - // rarely ever called. + // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that + // is rarely ever called. unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; for (const auto& kv : mAlarms) { if (firedAlarms.count(kv.second) > 0) { diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index 052fdf576289..ba687dacf519 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -53,7 +53,8 @@ public: // and removes it from firedAlarms. // Note that this will generally be called from a different thread from the other functions; // the caller is responsible for thread safety. - void informAlarmsFired(const uint64_t& timestampNs, + void informAlarmsFired( + const uint64_t& timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override; protected: diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index e58c53506c54..4c6a36bd9270 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -24,6 +24,7 @@ option java_outer_classname = "AtomsProto"; 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"; @@ -98,6 +99,7 @@ message Atom { 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. } @@ -725,6 +727,33 @@ message BootSequenceReported { 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 * @@ -1019,6 +1048,138 @@ message ForegroundServiceStateChanged { } /** + * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky + * behavior in its own uid. However, the metrics of these isolated uid's almost always should be + * attributed back to the parent (host) uid. One example is Chrome. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message IsolatedUidChanged { + // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid. + optional int32 parent_uid = 1; + + optional int32 isolated_uid = 2; + + // We expect an isolated uid to be removed before if it's used for another parent uid. + enum Event { + REMOVED = 0; + CREATED = 1; + } + optional Event event = 3; +} + +/* + * Logs the reception of an incoming network packet causing the main system to wake up for + * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd + * and processed by WakeupController.cpp. + */ +message PacketWakeupOccurred { + // The uid owning the socket into which the packet was delivered, or -1 if the packet was + // delivered nowhere. + optional int32 uid = 1; + // The interface name on which the packet was received. + optional string iface = 2; + // The ethertype value of the packet. + optional int32 ethertype = 3; + // String representation of the destination MAC address of the packet. + optional string destination_hardware_address = 4; + // String representation of the source address of the packet if this was an IP packet. + optional string source_ip = 5; + // String representation of the destination address of the packet if this was an IP packet. + optional string destination_ip = 6; + // The value of the protocol field if this was an IPv4 packet or the value of the Next Header + // field if this was an IPv6 packet. The range of possible values is the same for both IP + // families. + optional int32 ip_next_header = 7; + // The source port if this was a TCP or UDP packet. + optional int32 source_port = 8; + // The destination port if this was a TCP or UDP packet. + optional int32 destination_port = 9; +} + +/* + * Logs the memory stats for an app on startup. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message AppStartMemoryStateCaptured { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // The activity name. + optional string activity_name = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +/* + * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries + * for LMK event. + * Logged from: + * system/core/lmkd/lmkd.c + */ +message LmkStateChanged { + enum State { + UNKNOWN = 0; + START = 1; + STOP = 2; + } + optional State state = 1; +} + +/* + * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure. + * Logged from: + * system/core/lmkd/lmkd.c + */ +message LmkKillOccurred { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // oom adj score. + optional int32 oom_score = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +////////////////////////////////////////////////////////////////////// +// Pulled atoms below this line // +////////////////////////////////////////////////////////////////////// + +/** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * * Pulled from: @@ -1134,37 +1295,15 @@ message KernelWakelock { * hardware/interfaces/power/1.1/types.hal */ message SubsystemSleepState { - // Name should be in the format of XXX.YYY where XXX is subsystem name, - // YYY is corresponding voter name. - // If there are no voters, the format should just be XXX (with no dot). - // XXX and YYY should not contain a "." in it. - optional string name = 1; + // Subsystem name + optional string subsystem_name = 1; + // For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty. + // For SubsystemLowPowerStats (hal 1.1), this is the sleep state name. + optional string subname = 2; // The number of times it entered, or voted for entering the sleep state - optional uint64 count = 2; + optional uint64 count = 3; // The length of time spent in, or spent voting for, the sleep state - optional uint64 timeMs = 3; -} - -/** - * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky - * behavior in its own uid. However, the metrics of these isolated uid's almost always should be - * attributed back to the parent (host) uid. One example is Chrome. - * - * Logged from: - * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java - */ -message IsolatedUidChanged { - // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid. - optional int32 parent_uid = 1; - - optional int32 isolated_uid = 2; - - // We expect an isolated uid to be removed before if it's used for another parent uid. - enum Event { - REMOVED = 0; - CREATED = 1; - } - optional Event event = 3; + optional uint64 timeMs = 4; } /** @@ -1201,35 +1340,6 @@ message CpuTimePerUidFreq { optional uint64 time_ms = 3; } -/* - * Logs the reception of an incoming network packet causing the main system to wake up for - * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd - * and processed by WakeupController.cpp. - */ -message PacketWakeupOccurred { - // The uid owning the socket into which the packet was delivered, or -1 if the packet was - // delivered nowhere. - optional int32 uid = 1; - // The interface name on which the packet was received. - optional string iface = 2; - // The ethertype value of the packet. - optional int32 ethertype = 3; - // String representation of the destination MAC address of the packet. - optional string destination_hardware_address = 4; - // String representation of the source address of the packet if this was an IP packet. - optional string source_ip = 5; - // String representation of the destination address of the packet if this was an IP packet. - optional string destination_ip = 6; - // The value of the protocol field if this was an IPv4 packet or the value of the Next Header - // field if this was an IPv6 packet. The range of possible values is the same for both IP - // families. - optional int32 ip_next_header = 7; - // The source port if this was a TCP or UDP packet. - optional int32 source_port = 8; - // The destination port if this was a TCP or UDP packet. - optional int32 destination_port = 9; -} - /** * Pulls Wifi Controller Activity Energy Info */ @@ -1303,37 +1413,6 @@ message BluetoothActivityInfo { } /* - * Logs the memory stats for an app on startup. - * Logged from: - * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java - */ -message AppStartMemoryStateCaptured { - // The uid if available. -1 means not available. - optional int32 uid = 1; - - // The process name. - optional string process_name = 2; - - // The activity name. - optional string activity_name = 3; - - // # of page-faults - optional int64 pgfault = 4; - - // # of major page-faults - optional int64 pgmajfault = 5; - - // RSS - optional int64 rss_in_bytes = 6; - - // CACHE - optional int64 cache_in_bytes = 7; - - // SWAP - optional int64 swap_in_bytes = 8; -} - -/* * Logs the memory stats for a process. */ message ProcessMemoryState { @@ -1363,52 +1442,6 @@ message ProcessMemoryState { } /* - * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries - * for LMK event. - * Logged from: - * system/core/lmkd/lmkd.c - */ -message LmkStateChanged { - enum State { - UNKNOWN = 0; - START = 1; - STOP = 2; - } - optional State state = 1; -} - -/* - * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure. - * Logged from: - * system/core/lmkd/lmkd.c - */ -message LmkKillOccurred { - // The uid if available. -1 means not available. - optional int32 uid = 1; - - // The process name. - optional string process_name = 2; - - // oom adj score. - optional int32 oom_score = 3; - - // # of page-faults - optional int64 pgfault = 4; - - // # of major page-faults - optional int64 pgmajfault = 5; - - // RSS - optional int64 rss_in_bytes = 6; - - // CACHE - optional int64 cache_in_bytes = 7; - - // SWAP - optional int64 swap_in_bytes = 8; -} - -/* * Elapsed real time from SystemClock. */ message SystemElapsedRealtime { diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 61eeee3796df..06ff603f083f 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -75,8 +75,8 @@ void ConfigManager::UpdateConfig(const ConfigKey& key, const StatsdConfig& confi } } -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 @@ vector<ConfigKey> ConfigManager::GetAllConfigKeys() const { 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 @@ void ConfigManager::Dump(FILE* out) { 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 ad666bc15b84..a2b2a0ce43d5 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 @@ public: /** * 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 @@ private: 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 a75127324745..d0d2f938cf0c 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 e7ea4b9abf0c..d9aeb4656bfe 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 7a2d1991a538..0e126e7a4b44 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 7426e743c40f..7684ed4eb44b 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 1d8a96850c4b..b09d373a5f3b 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 os { 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 @@ bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config) { 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 b955f1cdaf5a..8210c8dcd63d 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 @@ bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* da 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 da14434737af..fc0ad7c5c3fc 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 @@ namespace statsd { 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 @@ bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) { 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 bc7c45f535d1..82a8611b295f 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 android { namespace os { namespace statsd { -class StatsPuller { +class StatsPuller : public virtual RefBase { public: StatsPuller(const int tagId); @@ -38,9 +41,15 @@ public: bool Pull(std::vector<std::shared_ptr<LogEvent>>* data); - void ClearCache(); + // Clear cache immediately + int ForceClearCache(); + + // Clear cache if elapsed time is more than cooldown time + int ClearCacheIfNecessary(long timestampSec); -protected: + static void SetUidMap(const sp<UidMap>& uidMap); + + protected: // The atom tag id this puller pulls const int mTagId; @@ -61,6 +70,10 @@ private: 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 4826d963a3ed..0dee342f0abe 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -54,8 +54,12 @@ class StatsPullerManager { mPullerManager.SetTimeBaseSec(timeBaseSec); } - void ClearPullerCache() { - mPullerManager.ClearPullerCache(); + int ForceClearPullerCache() { + return mPullerManager.ForceClearPullerCache(); + } + + int ClearPullerCacheIfNecessary(long timestampSec) { + return mPullerManager.ClearPullerCacheIfNecessary(timestampSec); } private: diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index 71b0abe25d28..4c676a70363f 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 android { 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 @@ StatsPullerManagerImpl& StatsPullerManagerImpl::GetInstance() { } 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::OnAlarmFired() { } } -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 fba3ade8c1bb..76a4c14e13b5 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.h +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h @@ -32,6 +32,20 @@ namespace android { 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 @@ public: void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;}; - void ClearPullerCache(); + int ForceClearPullerCache(); + + int ClearPullerCacheIfNecessary(long timestampSec); + + const static std::map<int, PullAtomInfo> kAllPullAtomInfo; -private: + private: StatsPullerManagerImpl(); // use this to update alarm @@ -59,9 +77,6 @@ private: 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 @@ private: // 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 6d12e257df8d..65a1df0eda20 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 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) auto statePtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE, timestamp); statePtr->write(state.name); + statePtr->write(""); statePtr->write(state.totalTransitions); statePtr->write(state.residencyInMsecSinceBoot); statePtr->init(); @@ -110,7 +111,8 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) for (auto voter : state.voters) { auto voterPtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE, timestamp); - voterPtr->write((std::string)state.name + "." + (std::string)voter.name); + voterPtr->write(state.name); + voterPtr->write(voter.name); voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot); voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); voterPtr->init(); @@ -144,7 +146,8 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) subsystem.states[j]; auto subsystemStatePtr = make_shared<LogEvent>( android::util::SUBSYSTEM_SLEEP_STATE, timestamp); - subsystemStatePtr->write((std::string)subsystem.name + "." + (std::string)state.name); + subsystemStatePtr->write(subsystem.name); + subsystemStatePtr->write(state.name); subsystemStatePtr->write(state.totalTransitions); subsystemStatePtr->write(state.residencyInMsecSinceBoot); subsystemStatePtr->init(); diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp new file mode 100644 index 000000000000..7cfc1d487dfb --- /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/core/java/com/android/internal/car/ICarServiceHelper.aidl b/cmds/statsd/src/external/puller_util.h index 9ee330be060b..70d5321ce29a 100644 --- a/core/java/com/android/internal/car/ICarServiceHelper.aidl +++ b/cmds/statsd/src/external/puller_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,21 @@ * limitations under the License. */ -package com.android.internal.car; +#pragma once -/** - * Helper API for car service. Only for itneraction between system server and car service. - * @hide - */ -interface ICarServiceHelper { -} +#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 e1947c4a5fe8..01c75873fdf9 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 @@ std::string dumpMemInfo(size_t limit) { 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 77f54569cb47..06c5b0049a3b 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 1f4bfa62c453..f254327fcc16 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -85,6 +85,9 @@ public: // Maximum size of all files that can be written to stats directory on disk. static const int kMaxFileSize = 50 * 1024 * 1024; + // How long to try to clear puller cache from last time + static const long kPullerCacheClearIntervalSec = 1; + /** * Report a new config has been received and report the static stats about the config. * diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 1dcd8534db79..e1ab5d529370 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 @@ int64_t LogEvent::GetLong(size_t key, status_t* err) const { } } +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 eb2c00845d00..d521e09569b6 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -76,6 +76,7 @@ public: * 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 5b5b57b61302..5a042b63c675 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -45,8 +45,6 @@ namespace statsd { // 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::onSlicedConditionMayChangeLocked(const uint64_t eventT 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 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, } 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,7 +163,6 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, } protoOutput->end(protoToken); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); mPastBuckets.clear(); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 2400eba19de9..65cbc4afdad4 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -44,8 +44,6 @@ namespace statsd { // 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; @@ -179,7 +177,6 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, 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 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, } 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,7 +246,6 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, } protoOutput->end(protoToken); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); mPastBuckets.clear(); } diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index a021e0a4d6c8..0578e0682687 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -42,8 +42,6 @@ namespace statsd { // 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 @@ void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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 ", diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 4190f0048cc2..62ee6ef17d5c 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -45,8 +45,6 @@ namespace statsd { // 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 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, } 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,7 +185,6 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->end(wrapperToken); } protoOutput->end(protoToken); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); mPastBuckets.clear(); // TODO: Clear mDimensionKeyMap once the report is dumped. diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 6573a898c55d..6c21b052e807 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" @@ -29,6 +29,7 @@ #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; @@ -48,7 +49,7 @@ const int FIELD_ID_METRICS = 1; MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, sp<UidMap> uidMap) - : mConfigKey(key), mUidMap(uidMap) { + : mConfigKey(key), mUidMap(uidMap), mLastReportTimeNs(0) { mConfigValid = initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap, @@ -184,6 +185,7 @@ void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) { protoOutput->end(token); } } + mLastReportTimeNs = ::android::elapsedRealtimeNano(); VLOG("=========================Metric Reports End=========================="); } diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 9cae70a9de80..2b30f44d345a 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -64,6 +64,11 @@ public: 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); @@ -78,6 +83,8 @@ private: bool mConfigValid = false; + uint64_t mLastReportTimeNs; + // The uid log sources from StatsdConfig. std::vector<int32_t> mAllowedUid; diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 31d9ff8455c8..7b1944c595e5 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -48,8 +48,6 @@ namespace statsd { // 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::onSlicedConditionMayChangeLocked(const uint64_t eventT 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 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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,7 +182,6 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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(); diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index a0173ee9922f..205c8e4b22cd 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 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, 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/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 691423e054b2..0d7b722c40bc 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" diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h index 3304f6c976aa..f1da452bf5b9 100644 --- a/cmds/statsd/src/packages/UidMap.h +++ b/cmds/statsd/src/packages/UidMap.h @@ -91,7 +91,7 @@ public: 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 af21ca4d82e8..b56cffb40806 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -143,9 +143,7 @@ message UidMapping { 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 @@ message ConfigMetricsReport { 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 3eaf7a17a3e2..5a326a47eb24 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -275,6 +275,12 @@ message Alarm { message IncidentdDetails { repeated int32 section = 1; + + enum Destination { + AUTOMATIC = 0; + EXPLICIT = 1; + } + optional Destination dest = 2; } message PerfettoDetails { diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 00d86582951f..23bd55616be2 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 000000000000..d9a8fc894804 --- /dev/null +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define DEBUG true +#include "Log.h" + +#include "IncidentdReporter.h" +#include "frameworks/base/libs/incident/proto/android/os/header.pb.h" + +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IBinder.h> +#include <binder/IServiceManager.h> + +namespace android { +namespace os { +namespace statsd { + +bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert, + const ConfigKey& configKey) { + if (config.section_size() == 0) { + VLOG("The alert %lld contains zero section in config(%d,%lld)", alert.id(), + configKey.GetUid(), (long long) configKey.GetId()); + return false; + } + + IncidentReportArgs incidentReport; + + android::os::IncidentHeaderProto header; + header.set_alert_id(alert.id()); + header.mutable_config_key()->set_uid(configKey.GetUid()); + header.mutable_config_key()->set_id(configKey.GetId()); + incidentReport.addHeader(header); + + for (int i = 0; i < config.section_size(); i++) { + incidentReport.addSection(config.section(i)); + } + + uint8_t dest; + switch (config.dest()) { + case IncidentdDetails_Destination_AUTOMATIC: + dest = android::os::DEST_AUTOMATIC; + break; + case IncidentdDetails_Destination_EXPLICIT: + dest = android::os::DEST_EXPLICIT; + break; + default: + dest = android::os::DEST_AUTOMATIC; + } + incidentReport.setDest(dest); + + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == nullptr) { + ALOGW("Failed to fetch incident service."); + return false; + } + VLOG("Calling incidentd %p", service.get()); + binder::Status s = service->reportIncident(incidentReport); + VLOG("Report incident status: %s", s.toString8().string()); + return s.isOk(); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/wifi/java/android/net/wifi/IRttManager.aidl b/cmds/statsd/src/subscriber/IncidentdReporter.h index 383180995b21..229ed778af3a 100644 --- a/wifi/java/android/net/wifi/IRttManager.aidl +++ b/cmds/statsd/src/subscriber/IncidentdReporter.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 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,15 +14,21 @@ * limitations under the License. */ -package android.net.wifi; -import android.os.Messenger; -import android.net.wifi.RttManager; +#pragma once + +#include "config/ConfigKey.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert, IncidentdDetails + +namespace android { +namespace os { +namespace statsd { /** - * {@hide} + * Calls incidentd to trigger an incident report and put in dropbox for uploading. */ -interface IRttManager -{ - Messenger getMessenger(in IBinder binder, out int[] key); - RttManager.RttCapabilities getRttCapabilities(); -} +bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert, + const ConfigKey& configKey); + +} // namespace statsd +} // namespace os +} // namespace android 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 000000000000..7d9c8a8208ee --- /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/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 0a97456c5684..b48de540ac09 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 @@ public: 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 cd76c9d379b5..52673fbdcf92 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 5d35c291f365..784ed40ce2c2 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 0eab0f4d0725..60948a181a1b 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 33c8abf03462..03e5fef5d717 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 @@ public class DisplayProtoUtils { 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 57575ae145f6..0e6c93316331 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 @@ public class MainActivity extends Activity { 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 @@ public class MainActivity extends Activity { 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 @@ public class MainActivity extends Activity { } } }); + + 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 @@ public class MainActivity extends Activity { 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 @@ public class MainActivity extends Activity { 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 @@ public class MainActivity extends Activity { .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 75489ade6432..87b82c261bf1 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 @@ public class DisplayProtoUtils { 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 @@ public class DisplayProtoUtils { } 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 652f6b2d08a9..bed4d9809692 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.EditText; 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 @@ import java.util.Map; * 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele Intent activityIntent = new Intent(context, LoadtestActivity.class); activityIntent.putExtra(TYPE, STOP); context.startActivity(activityIntent); - } + } } private static Map<String, TimeUnit> initializeTimeUnitMap() { @@ -119,6 +121,7 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele labels.put("1s", TimeUnit.CTS); return labels; } + private static List<String> initializeTimeUnitLabels() { List<String> labels = new ArrayList(); labels.add("1s"); @@ -136,10 +139,14 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele */ 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele @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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele } // 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele // 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele } 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 @@ public class LoadtestActivity extends Activity implements AdapterView.OnItemSele 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 58936fcc4452..71de479b5530 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -7,15 +7,18 @@ Landroid/app/Activity;->convertToTranslucent(Landroid/app/Activity$TranslucentCo 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;->BIND_SERVICE:I 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;->mNumVisibleActivities:I 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/Strin Landroid/app/admin/DevicePolicyManager;->packageHasActiveAdmins(Ljava/lang/String;)Z Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;Z)V +Landroid/app/AlarmManager;->FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED:I +Landroid/app/AlarmManager;->FLAG_IDLE_UNTIL:I +Landroid/app/AlarmManager;->FLAG_STANDALONE:I +Landroid/app/AlarmManager;->FLAG_WAKE_FROM_IDLE:I +Landroid/app/AlarmManager;->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_WIFI_SCAN:I 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;->forceStopPackage(Ljava/lang/String;I)V 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;->expandNotificationsPanel()V 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;->bindAppWidgetIdIfAllowed(IILandroid/conten 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;->close()V 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;->getContentService()Landroid/content/IContentS 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;->cancelSync(Landroid/accounts/Account;Ljava/la 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;->isPrivilegedApp()Z 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;->getPackageSizeInfo(Ljava/lang/String;Landro 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/PackageParser;->parsePackage(Ljava/io/File;IZ)Landroid/conte Landroid/content/pm/UserInfo;->id:I Landroid/content/pm/UserInfo;->isPrimary()Z Landroid/content/res/AssetManager;->addAssetPath(Ljava/lang/String;)I -Landroid/content/res/AssetManager;->addAssetPaths([Ljava/lang/String;)[I -Landroid/content/res/AssetManager;->applyStyle(JIIJ[IIJJ)V -Landroid/content/res/AssetManager;->getArraySize(I)I Landroid/content/res/AssetManager;->getAssignedPackageIdentifiers()Landroid/util/SparseArray; -Landroid/content/res/AssetManager;->getCookieName(I)Ljava/lang/String; Landroid/content/res/AssetManager;->getResourceBagText(II)Ljava/lang/CharSequence; -Landroid/content/res/AssetManager;->loadResourceBagValue(IILandroid/util/TypedValue;Z)I -Landroid/content/res/AssetManager;->loadResourceValue(ISLandroid/util/TypedValue;Z)I -Landroid/content/res/AssetManager;->loadThemeAttributeValue(JILandroid/util/TypedValue;Z)I Landroid/content/res/AssetManager;->mObject:J -Landroid/content/res/AssetManager;->openNonAssetFdNative(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor; Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;I)Ljava/io/InputStream; Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;)Ljava/io/InputStream; Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;I)Ljava/io/InputStream; Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;)Ljava/io/InputStream; -Landroid/content/res/AssetManager;->openNonAssetNative(ILjava/lang/String;I)J -Landroid/content/res/AssetManager;->openXmlAssetNative(ILjava/lang/String;)J -Landroid/content/res/AssetManager;->resolveAttrs(JII[I[I[I[I)Z -Landroid/content/res/AssetManager;->retrieveArray(I[I)I -Landroid/content/res/AssetManager;->retrieveAttributes(J[I[I[I)Z -Landroid/content/res/AssetManager;->STYLE_NUM_ENTRIES:I -Landroid/content/res/AssetManager;->STYLE_RESOURCE_ID:I Landroid/content/res/DrawableCache;->getInstance(JLandroid/content/res/Resources;Landroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable; Landroid/content/res/Resources;->getCompatibilityInfo()Landroid/content/res/CompatibilityInfo; Landroid/content/res/ResourcesImpl;->mAccessLock:Ljava/lang/Object; @@ -271,7 +318,6 @@ Landroid/content/res/TypedArray;->mResources:Landroid/content/res/Resources; Landroid/content/res/TypedArray;->mTheme:Landroid/content/res/Resources$Theme; Landroid/content/res/TypedArray;->mValue:Landroid/util/TypedValue; Landroid/content/res/TypedArray;->mXml:Landroid/content/res/XmlBlock$Parser; -Landroid/content/res/XmlBlock;->close()V Landroid/content/res/XmlBlock;->newParser()Landroid/content/res/XmlResourceParser; Landroid/content/res/XmlBlock$Parser;->mParseState:J Landroid/content/SyncStatusInfo;->lastSuccessTime:J @@ -301,6 +347,11 @@ Landroid/graphics/drawable/StateListDrawable;->getStateDrawable(I)Landroid/graph Landroid/graphics/drawable/StateListDrawable;->getStateSet(I)[I Landroid/graphics/drawable/StateListDrawable;->mStateListState:Landroid/graphics/drawable/StateListDrawable$StateListState; Landroid/graphics/drawable/StateListDrawable;->updateStateFromTypedArray(Landroid/content/res/TypedArray;)V +Landroid/graphics/FontFamily;->abortCreation()V +Landroid/graphics/FontFamily;->addFontFromAssetManager(Landroid/content/res/AssetManager;Ljava/lang/String;IZIII[Landroid/graphics/fonts/FontVariationAxis;)Z +Landroid/graphics/FontFamily;->addFontFromBuffer(Ljava/nio/ByteBuffer;I[Landroid/graphics/fonts/FontVariationAxis;II)Z +Landroid/graphics/FontFamily;->freeze()Z +Landroid/graphics/FontFamily;-><init>()V Landroid/graphics/LinearGradient;->mColors:[I Landroid/graphics/NinePatch;->mBitmap:Landroid/graphics/Bitmap; Landroid/graphics/SurfaceTexture;->nativeDetachFromGLContext()I @@ -310,18 +361,64 @@ Landroid/graphics/Typeface;->setDefault(Landroid/graphics/Typeface;)V 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;->setMasterMute(ZI)V 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;->setLooping(Z)V 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;->isNetworkSupported(I)Z 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;->tag:[I 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;->mTaskInvoked:Ljava/util/concurrent/atomic/AtomicBoolean; Landroid/os/AsyncTask;->mWorker:Landroid/os/AsyncTask$WorkerRunnable; Landroid/os/AsyncTask;->sDefaultExecutor:Ljava/util/concurrent/Executor; Landroid/os/AsyncTask;->setDefaultExecutor(Ljava/util/concurrent/Executor;)V +Landroid/os/BatteryStats$HistoryItem;->states2:I Landroid/os/BatteryStats;->NUM_DATA_CONNECTION_TYPES:I Landroid/os/BatteryStats$Timer;->getTotalTimeLocked(JI)J Landroid/os/BatteryStats$Uid;->getFullWifiLockTime(JI)J @@ -546,27 +734,40 @@ Landroid/os/Trace;->TRACE_TAG_VIEW:J 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;->onConnect(Ljava/lang/Stri 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;->getSubId()I 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;->mSpanEnds:[I 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;->setAnimationScale(IF)V 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;->mVideoHeight:I Landroid/widget/VideoView;->mVideoWidth:I Lcom/android/internal/app/IBatteryStats;->getStatistics()[B Lcom/android/internal/app/IBatteryStats$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IBatteryStats; +Lcom/android/internal/os/BatterySipper;->add(Lcom/android/internal/os/BatterySipper;)V +Lcom/android/internal/os/BatterySipper;->drainType:Lcom/android/internal/os/BatterySipper$DrainType; +Lcom/android/internal/os/BatterySipper;->getUid()I +Lcom/android/internal/os/BatterySipper;-><init>(Lcom/android/internal/os/BatterySipper$DrainType;Landroid/os/BatteryStats$Uid;D)V +Lcom/android/internal/os/BatterySipper;->mPackages:[Ljava/lang/String; +Lcom/android/internal/os/BatterySipper;->packageWithHighestDrain:Ljava/lang/String; +Lcom/android/internal/os/BatterySipper;->totalPowerMah:D +Lcom/android/internal/os/BatterySipper;->uidObj:Landroid/os/BatteryStats$Uid; +Lcom/android/internal/os/BatteryStatsHelper;->getMaxPower()D +Lcom/android/internal/os/BatteryStatsHelper;->getStats()Landroid/os/BatteryStats; +Lcom/android/internal/os/BatteryStatsHelper;->getTotalPower()D +Lcom/android/internal/os/BatteryStatsHelper;-><init>(Landroid/content/Context;ZZ)V +Lcom/android/internal/os/BatteryStatsHelper;->load()V +Lcom/android/internal/os/BatteryStatsHelper;->mBatteryInfo:Lcom/android/internal/app/IBatteryStats; +Lcom/android/internal/os/BatteryStatsHelper;->mPowerProfile:Lcom/android/internal/os/PowerProfile; +Lcom/android/internal/os/BatteryStatsHelper;->mUsageList:Ljava/util/List; +Lcom/android/internal/os/BatteryStatsHelper;->refreshStats(II)V Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryRealtime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryUptime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->CREATOR:Landroid/os/Parcelable$Creator; +Lcom/android/internal/os/BatteryStatsImpl;->getDischargeAmount(I)I Lcom/android/internal/os/BatteryStatsImpl;->getGlobalWifiRunningTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->getScreenOnTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->getUidStats()Landroid/util/SparseArray; @@ -1091,6 +1373,9 @@ Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getAlpnSelectedProtocol()[B 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 0bc510a13ba6..aca8d4819cf6 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6362,6 +6362,8 @@ public class Activity extends ContextThemeWrapper } else { writer.print(prefix); writer.println("No AutofillManager"); } + + ResourcesManager.getInstance().dump(prefix, writer); } /** diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3d088ff9e5c3..ff970bf82a7a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -113,6 +113,7 @@ import android.util.DisplayMetrics; 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; @@ -204,7 +205,7 @@ public final class ActivityThread extends ClientTransactionHandler { 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; @@ -398,7 +399,6 @@ public final class ActivityThread extends ClientTransactionHandler { boolean startsNotResumed; public final boolean isForward; int pendingConfigChanges; - boolean onlyLocalRequest; Window mPendingRemoveWindow; WindowManager mPendingRemoveWindowManager; @@ -520,7 +520,6 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +764,6 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +1521,6 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +1566,6 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +1599,6 @@ public final class ActivityThread extends ClientTransactionHandler { 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; @@ -3726,20 +3708,6 @@ public final class ActivityThread extends ClientTransactionHandler { } 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 +3856,12 @@ public final class ActivityThread extends ClientTransactionHandler { } } - 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. @@ -4586,15 +4552,12 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +4579,31 @@ public final class ActivityThread extends ClientTransactionHandler { 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); - } - - if (fromServer) { - target.startsNotResumed = notResumed; - target.onlyLocalRequest = false; - } - if (config != null) { - target.createdConfig = config; - } - if (overrideConfig != null) { - target.overrideConfig = overrideConfig; + scheduleRelaunch = true; } + 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 +4672,10 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +4692,9 @@ public final class ActivityThread extends ClientTransactionHandler { // 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(); @@ -4804,24 +4733,22 @@ public final class ActivityThread extends ClientTransactionHandler { 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 +5817,23 @@ public final class ActivityThread extends ClientTransactionHandler { // 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 5b61fdf8677f..114d18b9a996 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.IBinder; +import android.util.MergedConfiguration; import com.android.internal.content.ReferrerIntent; @@ -124,6 +125,39 @@ public abstract class ClientTransactionHandler { 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 4a9b2bcb16ed..a1ba13d9512c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -409,6 +409,7 @@ class ContextImpl extends Context { 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 2b648ea6937d..eb260265c15f 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -609,18 +609,19 @@ public class Dialog implements DialogInterface, Window.Callback, /** * 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 @@ public class Dialog implements DialogInterface, Window.Callback, /** * 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 9e99a78ccfbd..ae9b83ec0122 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -83,9 +83,6 @@ oneway interface IApplicationThread { 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/Notification.java b/core/java/android/app/Notification.java index 8393caa33e31..c80565869f56 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4616,10 +4616,15 @@ public class Notification implements Parcelable 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 { @@ -6434,7 +6439,7 @@ public class Notification implements Parcelable public RemoteViews makeContentView(boolean increasedHeight) { mBuilder.mOriginalActions = mBuilder.mActions; mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); mBuilder.mActions = mBuilder.mOriginalActions; mBuilder.mOriginalActions = null; return remoteViews; @@ -6469,11 +6474,11 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeBigContentView(false /* showRightIcon */); + return makeMessagingView(false /* isCollapsed */); } @NonNull - private RemoteViews makeBigContentView(boolean showRightIcon) { + private RemoteViews makeMessagingView(boolean isCollapsed) { CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; @@ -6484,21 +6489,24 @@ public class Notification implements Parcelable nameReplacement = conversationTitle; conversationTitle = null; } + boolean hideLargeIcon = !isCollapsed || isOneToOne; RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null) - .hideLargeIcon(!showRightIcon || isOneToOne) + .hideLargeIcon(hideLargeIcon) .headerTextSecondary(conversationTitle) - .alwaysShowReply(showRightIcon)); + .alwaysShowReply(isCollapsed)); addExtras(mBuilder.mN.extras); // also update the end margin if there is an image int endMargin = R.dimen.notification_content_margin_end; - if (mBuilder.mN.hasLargeIcon() && showRightIcon) { + if (isCollapsed) { endMargin = R.dimen.notification_content_plus_picture_margin_end; } contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.resolveContrastColor()); + contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", + isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", @@ -6565,7 +6573,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */); + RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); return remoteViews; } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index fb11272d7e62..b96e028076e5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -21,6 +21,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; @@ -34,6 +35,7 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; +import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; @@ -41,9 +43,13 @@ import android.view.DisplayAdjustments; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.IndentingPrintWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -59,12 +65,7 @@ public class ResourcesManager { * Predicate that returns true if a WeakReference is gc'ed. */ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = - new Predicate<WeakReference<Resources>>() { - @Override - public boolean test(WeakReference<Resources> weakRef) { - return weakRef == null || weakRef.get() == null; - } - }; + weakRef -> weakRef == null || weakRef.get() == null; /** * The global compatibility settings. @@ -89,6 +90,48 @@ public class ResourcesManager { */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); + private static class ApkKey { + public final String path; + public final boolean sharedLib; + public final boolean overlay; + + ApkKey(String path, boolean sharedLib, boolean overlay) { + this.path = path; + this.sharedLib = sharedLib; + this.overlay = overlay; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + this.path.hashCode(); + result = 31 * result + Boolean.hashCode(this.sharedLib); + result = 31 * result + Boolean.hashCode(this.overlay); + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ApkKey)) { + return false; + } + ApkKey other = (ApkKey) obj; + return this.path.equals(other.path) && this.sharedLib == other.sharedLib + && this.overlay == other.overlay; + } + } + + /** + * The ApkAssets we are caching and intend to hold strong references to. + */ + private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15); + + /** + * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't + * in our LRU cache. Bonus resources :) + */ + private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); + /** * Resources and base configuration override associated with an Activity. */ @@ -260,6 +303,41 @@ public class ResourcesManager { } } + private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) + throws IOException { + final ApkKey newKey = new ApkKey(path, sharedLib, overlay); + ApkAssets apkAssets = mLoadedApkAssets.get(newKey); + if (apkAssets != null) { + return apkAssets; + } + + // Optimistically check if this ApkAssets exists somewhere else. + final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); + if (apkAssetsRef != null) { + apkAssets = apkAssetsRef.get(); + if (apkAssets != null) { + mLoadedApkAssets.put(newKey, apkAssets); + return apkAssets; + } else { + // Clean up the reference. + mCachedApkAssets.remove(newKey); + } + } + + // We must load this from disk. + if (overlay) { + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); + } + mLoadedApkAssets.put(newKey, apkAssets); + mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); + return apkAssets; + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -270,13 +348,15 @@ public class ResourcesManager { */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { - AssetManager assets = new AssetManager(); + final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { - if (assets.addAssetPath(key.mResDir) == 0) { + try { + apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } @@ -284,7 +364,10 @@ public class ResourcesManager { if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { - if (assets.addAssetPath(splitResDir) == 0) { + try { + apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/, + false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } @@ -293,7 +376,13 @@ public class ResourcesManager { if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { - assets.addOverlayPath(idmapPath); + try { + apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/)); + } catch (IOException e) { + Log.w(TAG, "failed to add overlay path " + idmapPath); + + // continue. + } } } @@ -302,16 +391,77 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. - if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { + try { + apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/)); + } catch (IOException e) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); + + // continue. } } } } + + AssetManager assets = new AssetManager(); + assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]), + false /*invalidateCaches*/); return assets; } + private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { + int count = 0; + for (WeakReference<T> ref : collection) { + final T value = ref != null ? ref.get() : null; + if (value != null) { + count++; + } + } + return count; + } + + /** + * @hide + */ + public void dump(String prefix, PrintWriter printWriter) { + synchronized (this) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + for (int i = 0; i < prefix.length() / 2; i++) { + pw.increaseIndent(); + } + + pw.println("ResourcesManager:"); + pw.increaseIndent(); + pw.print("cached apks: total="); + pw.print(mLoadedApkAssets.size()); + pw.print(" created="); + pw.print(mLoadedApkAssets.createCount()); + pw.print(" evicted="); + pw.print(mLoadedApkAssets.evictionCount()); + pw.print(" hit="); + pw.print(mLoadedApkAssets.hitCount()); + pw.print(" miss="); + pw.print(mLoadedApkAssets.missCount()); + pw.print(" max="); + pw.print(mLoadedApkAssets.maxSize()); + pw.println(); + + pw.print("total apks: "); + pw.println(countLiveReferences(mCachedApkAssets.values())); + + pw.print("resources: "); + + int references = countLiveReferences(mResourceReferences); + for (ActivityResources activityResources : mActivityResourceReferences.values()) { + references += countLiveReferences(activityResources.activityResources); + } + pw.println(references); + + pw.print("resource impls: "); + pw.println(countLiveReferences(mResourceImpls.values())); + } + } + private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { Configuration config; final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); @@ -630,28 +780,16 @@ public class ResourcesManager { // We will create the ResourcesImpl object outside of holding this lock. } - } - - // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. - ResourcesImpl resourcesImpl = createResourcesImpl(key); - if (resourcesImpl == null) { - return null; - } - synchronized (this) { - ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); - if (existingResourcesImpl != null) { - if (DEBUG) { - Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl - + " new impl=" + resourcesImpl); - } - resourcesImpl.getAssets().close(); - resourcesImpl = existingResourcesImpl; - } else { - // Add this ResourcesImpl to the cache. - mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. + ResourcesImpl resourcesImpl = createResourcesImpl(key); + if (resourcesImpl == null) { + return null; } + // Add this ResourcesImpl to the cache. + mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + final Resources resources; if (activityToken != null) { resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 256c47934dc5..ea0fd75bec90 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -471,14 +471,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * {@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 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * {@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 c525c89c88e7..c2c91c2bcbd6 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -30,20 +30,27 @@ import android.util.Slog; * @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 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem } /** + * 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 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem 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 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem /** * 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 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem } /** + * 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 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem 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 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem 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 87f32b2613bc..aa52cdef70c6 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -38,7 +38,6 @@ import android.content.ClipboardManager; 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.ILowpanManager; 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.ISystemUpdateManager; 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 @@ final class SystemServiceRegistry { 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/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index 08effd9c148a..202b89496007 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -77,6 +77,7 @@ public class SecurityLog { TAG_KEY_DESTRUCTION, TAG_CERT_AUTHORITY_INSTALLED, TAG_CERT_AUTHORITY_REMOVED, + TAG_CRYPTO_SELF_TEST_COMPLETED, }) public @interface SecurityLogTag {} @@ -400,6 +401,14 @@ public class SecurityLog { 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 @@ public class SecurityLog { 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 be626786c3c6..b64b7e3e4432 100644 --- a/core/java/android/app/admin/SecurityLogTags.logtags +++ b/core/java/android/app/admin/SecurityLogTags.logtags @@ -34,4 +34,5 @@ option java_package android.app.admin 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 000000000000..d8a7463cb086 --- /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/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java index 073d28cfa27f..af7b7a21a582 100644 --- a/core/java/android/app/servertransaction/PendingTransactionActions.java +++ b/core/java/android/app/servertransaction/PendingTransactionActions.java @@ -44,6 +44,7 @@ public class PendingTransactionActions { private boolean mCallOnPostCreate; private Bundle mOldState; private StopInfo mStopInfo; + private boolean mReportRelaunchToWM; public PendingTransactionActions() { clear(); @@ -91,6 +92,24 @@ public class PendingTransactionActions { 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/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index edb992bd265c..6b573e99f1fa 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 @@ public final class UsageEvents implements Parcelable { * 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 @@ public final class UsageEvents implements Parcelable { 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/content/Context.java b/core/java/android/content/Context.java index b85a3199881c..15f3777b99cc 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -560,6 +560,7 @@ public abstract class Context { * * @param resId Resource id for the CharSequence text */ + @NonNull public final CharSequence getText(@StringRes int resId) { return getResources().getText(resId); } @@ -616,12 +617,11 @@ public abstract class Context { * @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 @@ public abstract class Context { * @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 @@ public abstract class Context { * @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 fa73e3cbab99..12d4079ce1f0 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2399,6 +2399,26 @@ public class Intent implements Parcelable, Cloneable { */ @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. * diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index db1630b67bd2..1adc9f503f75 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1601,7 +1601,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public boolean isAllowedToUseHiddenApi() { - return isSystemApp(); + return isSystemApp() || isUpdatedSystemApp(); } /** diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java index 38f02256ee6e..361d4e4acb3b 100644 --- a/core/java/android/content/pm/InstantAppRequest.java +++ b/core/java/android/content/pm/InstantAppRequest.java @@ -18,12 +18,14 @@ package android.content.pm; import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; /** * Information needed to make an instant application resolution request. * @hide */ public final class InstantAppRequest { + /** Response from the first phase of instant application resolution */ public final AuxiliaryResolveInfo responseObj; /** The original intent that triggered instant application resolution */ @@ -40,6 +42,8 @@ public final class InstantAppRequest { public final Bundle verificationBundle; /** Whether resolution occurs because an application is starting */ public final boolean resolveForStart; + /** The instant app digest for this request */ + public final InstantAppResolveInfo.InstantAppDigest digest; public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, int userId, Bundle verificationBundle, @@ -51,5 +55,11 @@ public final class InstantAppRequest { this.userId = userId; this.verificationBundle = verificationBundle; this.resolveForStart = resolveForStart; + if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) { + digest = new InstantAppResolveInfo.InstantAppDigest( + origIntent.getData().getHost(), 5 /*maxDigests*/); + } else { + digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED; + } } } diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 112c5dae6731..3a95a5f87d97 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -26,10 +26,13 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Random; /** * Describes an externally resolvable instant application. There are three states that this class @@ -227,14 +230,25 @@ public final class InstantAppResolveInfo implements Parcelable { */ @SystemApi public static final class InstantAppDigest implements Parcelable { - private static final int DIGEST_MASK = 0xfffff000; - + static final int DIGEST_MASK = 0xfffff000; public static final InstantAppDigest UNDEFINED = new InstantAppDigest(new byte[][]{}, new int[]{}); + + private static Random sRandom = null; + static { + try { + sRandom = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + // oh well + sRandom = new Random(); + } + } /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; - /** The first 4 bytes of the domain hashes */ + /** The first 5 bytes of the domain hashes */ private final int[] mDigestPrefix; + /** The first 5 bytes of the domain hashes interspersed with random data */ + private int[] mDigestPrefixSecure; public InstantAppDigest(@NonNull String hostName) { this(hostName, -1 /*maxDigests*/); @@ -306,6 +320,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } mDigestPrefix = in.createIntArray(); + mDigestPrefixSecure = in.createIntArray(); } public byte[][] getDigestBytes() { @@ -316,6 +331,26 @@ public final class InstantAppResolveInfo implements Parcelable { return mDigestPrefix; } + /** + * Returns a digest prefix with additional random prefixes interspersed. + * @hide + */ + public int[] getDigestPrefixSecure() { + if (this == InstantAppResolveInfo.InstantAppDigest.UNDEFINED) { + return getDigestPrefix(); + } else if (mDigestPrefixSecure == null) { + // let's generate some random data to intersperse throughout the set of prefixes + final int realSize = getDigestPrefix().length; + final int manufacturedSize = realSize + 10 + sRandom.nextInt(10); + mDigestPrefixSecure = Arrays.copyOf(getDigestPrefix(), manufacturedSize); + for (int i = realSize; i < manufacturedSize; i++) { + mDigestPrefixSecure[i] = sRandom.nextInt() & DIGEST_MASK; + } + Arrays.sort(mDigestPrefixSecure); + } + return mDigestPrefixSecure; + } + @Override public int describeContents() { return 0; @@ -323,6 +358,11 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + final boolean isUndefined = this == UNDEFINED; + out.writeBoolean(isUndefined); + if (isUndefined) { + return; + } if (mDigestBytes == null) { out.writeInt(-1); } else { @@ -332,6 +372,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } out.writeIntArray(mDigestPrefix); + out.writeIntArray(mDigestPrefixSecure); } @SuppressWarnings("hiding") @@ -339,6 +380,9 @@ public final class InstantAppResolveInfo implements Parcelable { new Parcelable.Creator<InstantAppDigest>() { @Override public InstantAppDigest createFromParcel(Parcel in) { + if (in.readBoolean() /* is undefined */) { + return UNDEFINED; + } return new InstantAppDigest(in); } @Override diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 09a46b8acf4b..0342c93bb34f 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -468,6 +468,18 @@ public class PackageInfo implements Parcelable { dest.writeBoolean(mOverlayIsStatic); dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); + writeSigningCertificateHistoryToParcel(dest, parcelableFlags); + } + + private void writeSigningCertificateHistoryToParcel(Parcel dest, int parcelableFlags) { + if (signingCertificateHistory != null) { + dest.writeInt(signingCertificateHistory.length); + for (int i = 0; i < signingCertificateHistory.length; i++) { + dest.writeTypedArray(signingCertificateHistory[i], parcelableFlags); + } + } else { + dest.writeInt(-1); + } } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -523,6 +535,7 @@ public class PackageInfo implements Parcelable { mOverlayIsStatic = source.readBoolean(); compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); + readSigningCertificateHistoryFromParcel(source); // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. @@ -534,6 +547,16 @@ public class PackageInfo implements Parcelable { } } + private void readSigningCertificateHistoryFromParcel(Parcel source) { + int len = source.readInt(); + if (len != -1) { + signingCertificateHistory = new Signature[len][]; + for (int i = 0; i < len; i++) { + signingCertificateHistory[i] = source.createTypedArray(Signature.CREATOR); + } + } + } + private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) { if (components != null) { for (ComponentInfo ci : components) { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index dda4167d3c3b..377942ad31f9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -54,6 +54,7 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -1286,7 +1287,6 @@ public class PackageParser { */ @Deprecated public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { - final AssetManager assets = newConfiguredAssetManager(); final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps) { if (!lite.coreApp) { @@ -1295,8 +1295,9 @@ public class PackageParser { } } + final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { - final Package pkg = parseBaseApk(apkFile, assets, flags); + final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags); pkg.setCodePath(apkFile.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; @@ -1304,26 +1305,8 @@ public class PackageParser { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to get path: " + apkFile, e); } finally { - IoUtils.closeQuietly(assets); - } - } - - private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParserException { - if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); - } - - // The AssetManager guarantees uniqueness for asset paths, so if this asset path - // already exists in the AssetManager, addAssetPath will only return the cookie - // assigned to it. - int cookie = assets.addAssetPath(apkPath); - if (cookie == 0) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + IoUtils.closeQuietly(assetLoader); } - return cookie; } private Package parseBaseApk(File apkFile, AssetManager assets, int flags) @@ -1341,13 +1324,15 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - - Resources res = null; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); @@ -1382,15 +1367,18 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - final Resources res; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + // This must always succeed, as the path has been added to the AssetManager before. + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } + parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); @@ -1592,21 +1580,19 @@ public class PackageParser { int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); - AssetManager assets = null; XmlResourceParser parser = null; try { - assets = newConfiguredAssetManager(); - int cookie = fd != null - ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath); - if (cookie == 0) { + final ApkAssets apkAssets; + try { + apkAssets = fd != null + ? ApkAssets.loadFromFd(fd, debugPathName, false, false) + : ApkAssets.loadFromPath(apkPath); + } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath); } - final DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - - parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { @@ -1633,7 +1619,7 @@ public class PackageParser { "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); - IoUtils.closeQuietly(assets); + // TODO(b/72056911): Implement and call close() on ApkAssets. } } diff --git a/core/java/android/content/pm/VerifierDeviceIdentity.java b/core/java/android/content/pm/VerifierDeviceIdentity.java index a8cdb6ae8af6..90be6f316ce6 100644 --- a/core/java/android/content/pm/VerifierDeviceIdentity.java +++ b/core/java/android/content/pm/VerifierDeviceIdentity.java @@ -19,6 +19,8 @@ package android.content.pm; 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 @@ public class VerifierDeviceIdentity implements Parcelable { * @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 02d0a6d8bd36..79bc9a30b1e2 100644 --- a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java +++ b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java @@ -274,6 +274,7 @@ public final class RuntimePermissionPresenter { } } + @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 99eb4702d32e..9e3a8f48996c 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java @@ -15,10 +15,13 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; +import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; @@ -26,6 +29,8 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; +import java.io.IOException; + /** * Loads the base and split APKs into a single AssetManager. * @hide @@ -33,68 +38,66 @@ import libcore.io.IoUtils; public class DefaultSplitAssetLoader implements SplitAssetLoader { private final String mBaseCodePath; private final String[] mSplitCodePaths; - private final int mFlags; - + private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; - public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) { + public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; mFlags = flags; } - private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParser.PackageParserException { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); } - if (assets.addAssetPath(apkPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + try { + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { if (mCachedAssetManager != null) { return mCachedAssetManager; } - AssetManager assets = new AssetManager(); - try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - loadApkIntoAssetManager(assets, mBaseCodePath, mFlags); - - if (!ArrayUtils.isEmpty(mSplitCodePaths)) { - for (String apkPath : mSplitCodePaths) { - loadApkIntoAssetManager(assets, apkPath, mFlags); - } - } + ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null + ? mSplitCodePaths.length : 0) + 1]; - mCachedAssetManager = assets; - assets = null; - return mCachedAssetManager; - } finally { - if (assets != null) { - IoUtils.closeQuietly(assets); + // Load the base. + int splitIdx = 0; + apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + + // Load any splits. + if (!ArrayUtils.isEmpty(mSplitCodePaths)) { + for (String apkPath : mSplitCodePaths) { + apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); } } + + AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + + mCachedAssetManager = assets; + return mCachedAssetManager; } @Override - public AssetManager getSplitAssetManager(int splitIdx) - throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { return getBaseAssetManager(); } @Override public void close() throws Exception { - if (mCachedAssetManager != null) { - IoUtils.closeQuietly(mCachedAssetManager); - } + IoUtils.closeQuietly(mCachedAssetManager); } } diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java index 16023f0d9d97..58eaabfa62f2 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java @@ -15,17 +15,21 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; +import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; import libcore.io.IoUtils; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -34,17 +38,15 @@ import java.util.Collections; * is to be used when an application opts-in to isolated split loading. * @hide */ -public class SplitAssetDependencyLoader - extends SplitDependencyLoader<PackageParser.PackageParserException> +public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException> implements SplitAssetLoader { private final String[] mSplitPaths; - private final int mFlags; - - private String[][] mCachedPaths; - private AssetManager[] mCachedAssetManagers; + private final @ParseFlags int mFlags; + private final ApkAssets[][] mCachedSplitApks; + private final AssetManager[] mCachedAssetManagers; public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, - SparseArray<int[]> dependencies, int flags) { + SparseArray<int[]> dependencies, @ParseFlags int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. @@ -53,7 +55,7 @@ public class SplitAssetDependencyLoader System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); mFlags = flags; - mCachedPaths = new String[mSplitPaths.length][]; + mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; mCachedAssetManagers = new AssetManager[mSplitPaths.length]; } @@ -62,58 +64,60 @@ public class SplitAssetDependencyLoader return mCachedAssetManagers[splitIdx] != null; } - private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags) - throws PackageParser.PackageParserException { - final AssetManager assets = new AssetManager(); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); + } + try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - - for (String assetPath : assetPaths) { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && - !PackageParser.isApkPath(assetPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + assetPath); - } - - if (assets.addAssetPath(assetPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + assetPath); - } - } - return assets; - } catch (Throwable e) { - IoUtils.closeQuietly(assets); - throw e; + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } + private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { + final AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + return assets; + } + @Override protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, - int parentSplitIdx) throws PackageParser.PackageParserException { - final ArrayList<String> assetPaths = new ArrayList<>(); + int parentSplitIdx) throws PackageParserException { + final ArrayList<ApkAssets> assets = new ArrayList<>(); + + // Include parent ApkAssets. if (parentSplitIdx >= 0) { - Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]); + Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); } - assetPaths.add(mSplitPaths[splitIdx]); + // Include this ApkAssets. + assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); + + // Load and include all config splits for this feature. for (int configSplitIdx : configSplitIndices) { - assetPaths.add(mSplitPaths[configSplitIdx]); + assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); } - mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]); - mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx], - mFlags); + + // Cache the results. + mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); + mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]); } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { loadDependenciesForSplit(0); return mCachedAssetManagers[0]; } @Override - public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int idx) throws PackageParserException { // Since we insert the base at position 0, and PackageParser keeps splits separate from // the base, we need to adjust the index. loadDependenciesForSplit(idx + 1); diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java new file mode 100644 index 000000000000..fd664bc731d0 --- /dev/null +++ b/core/java/android/content/res/ApkAssets.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.res; + +import android.annotation.NonNull; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * The loaded, immutable, in-memory representation of an APK. + * + * The main implementation is native C++ and there is very little API surface exposed here. The APK + * is mainly accessed via {@link AssetManager}. + * + * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, + * making the creation of AssetManagers very cheap. + * @hide + */ +public final class ApkAssets { + @GuardedBy("this") private final long mNativePtr; + @GuardedBy("this") private StringBlock mStringBlock; + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { + return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system) + throws IOException { + return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, + boolean forceSharedLibrary) throws IOException { + return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications. + * + * Performs a dup of the underlying fd, so you must take care of still closing + * the FileDescriptor yourself (and can do that whenever you want). + * + * @param fd The FileDescriptor of an open, readable APK. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLibrary) + throws IOException { + return new ApkAssets(fd, friendlyName, system, forceSharedLibrary); + } + + /** + * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path + * is encoded within the IDMAP. + * + * @param idmapPath Path to the IDMAP of an overlay APK. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) + throws IOException { + return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/); + } + + private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException { + Preconditions.checkNotNull(path, "path"); + mNativePtr = nativeLoad(path, system, forceSharedLib, overlay); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system, + boolean forceSharedLib) throws IOException { + Preconditions.checkNotNull(fd, "fd"); + Preconditions.checkNotNull(friendlyName, "friendlyName"); + mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + @NonNull String getAssetPath() { + synchronized (this) { + return nativeGetAssetPath(mNativePtr); + } + } + + CharSequence getStringFromPool(int idx) { + synchronized (this) { + return mStringBlock.get(idx); + } + } + + /** + * Retrieve a parser for a compiled XML file. This is associated with a single APK and + * <em>NOT</em> a full AssetManager. This means that shared-library references will not be + * dynamically assigned runtime package IDs. + * + * @param fileName The path to the file within the APK. + * @return An XmlResourceParser. + * @throws IOException if the file was not found or an error occurred retrieving it. + */ + public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); + synchronized (this) { + long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); + try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { + XmlResourceParser parser = block.newParser(); + // If nativeOpenXml doesn't throw, it will always return a valid native pointer, + // which makes newParser always return non-null. But let's be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } + } + } + + /** + * Returns false if the underlying APK was changed since this ApkAssets was loaded. + */ + public boolean isUpToDate() { + synchronized (this) { + return nativeIsUpToDate(mNativePtr); + } + } + + @Override + protected void finalize() throws Throwable { + nativeDestroy(mNativePtr); + } + + private static native long nativeLoad( + @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException; + private static native long nativeLoadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLib) + throws IOException; + private static native void nativeDestroy(long ptr); + private static native @NonNull String nativeGetAssetPath(long ptr); + private static native long nativeGetStringBlock(long ptr); + private static native boolean nativeIsUpToDate(long ptr); + private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 78665609bdd4..bb907005b680 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -18,9 +18,11 @@ package android.content.res; import android.annotation.AnyRes; import android.annotation.ArrayRes; +import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; +import android.annotation.StyleRes; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; import android.os.ParcelFileDescriptor; @@ -28,10 +30,20 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; -import java.io.FileDescriptor; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import libcore.io.IoUtils; + +import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -42,7 +54,19 @@ import java.util.HashMap; * bytes. */ public final class AssetManager implements AutoCloseable { - /* modes used when opening an asset */ + private static final String TAG = "AssetManager"; + private static final boolean DEBUG_REFS = false; + + private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk"; + + private static final Object sSync = new Object(); + + private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0]; + + // Not private for LayoutLib's BridgeAssetManager. + @GuardedBy("sSync") static AssetManager sSystem = null; + + @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0]; /** * Mode for {@link #open(String, int)}: no specific information about how @@ -65,87 +89,334 @@ public final class AssetManager implements AutoCloseable { */ public static final int ACCESS_BUFFER = 3; - private static final String TAG = "AssetManager"; - private static final boolean localLOGV = false || false; - - private static final boolean DEBUG_REFS = false; - - private static final Object sSync = new Object(); - /*package*/ static AssetManager sSystem = null; + @GuardedBy("this") private final TypedValue mValue = new TypedValue(); + @GuardedBy("this") private final long[] mOffsets = new long[2]; - private final TypedValue mValue = new TypedValue(); - private final long[] mOffsets = new long[2]; - - // For communication with native code. - private long mObject; + // Pointer to native implementation, stuffed inside a long. + @GuardedBy("this") private long mObject; + + // The loaded asset paths. + @GuardedBy("this") private ApkAssets[] mApkAssets; + + // Debug/reference counting implementation. + @GuardedBy("this") private boolean mOpen = true; + @GuardedBy("this") private int mNumRefs = 1; + @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; - private StringBlock mStringBlocks[] = null; - - private int mNumRefs = 1; - private boolean mOpen = true; - private HashMap<Long, RuntimeException> mRefStacks; - /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the * appropriate asset manager with {@link Resources#getAssets}. Not for * use by applications. - * {@hide} + * @hide */ public AssetManager() { - synchronized (this) { - if (DEBUG_REFS) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); - } - init(false); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); - ensureSystemAssets(); + final ApkAssets[] assets; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + assets = sSystemApkAssets; } - } - private static void ensureSystemAssets() { - synchronized (sSync) { - if (sSystem == null) { - AssetManager system = new AssetManager(true); - system.makeStringBlocks(null); - sSystem = system; - } + mObject = nativeCreate(); + if (DEBUG_REFS) { + mNumRefs = 0; + incRefsLocked(hashCode()); } + + // Always set the framework resources. + setApkAssets(assets, false /*invalidateCaches*/); } - - private AssetManager(boolean isSystem) { + + /** + * Private constructor that doesn't call ensureSystemAssets. + * Used for the creation of system assets. + */ + @SuppressWarnings("unused") + private AssetManager(boolean sentinel) { + mObject = nativeCreate(); if (DEBUG_REFS) { - synchronized (this) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); + mNumRefs = 0; + incRefsLocked(hashCode()); + } + } + + /** + * This must be called from Zygote so that system assets are shared by all applications. + */ + @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); + } + } + + /** + * Loads the static runtime overlays declared in /data/resource-cache/overlays.list. + * Throws an exception if the file is corrupt or if loading the APKs referenced by the file + * fails. Returns quietly if the overlays.list file doesn't exist. + * @param outApkAssets The list to fill with the loaded ApkAssets. + */ + private static void loadStaticRuntimeOverlays(ArrayList<ApkAssets> outApkAssets) + throws IOException { + final FileInputStream fis; + try { + fis = new FileInputStream("/data/resource-cache/overlays.list"); + } catch (FileNotFoundException e) { + // We might not have any overlays, this is fine. We catch here since ApkAssets + // loading can also fail with the same exception, which we would want to propagate. + Log.i(TAG, "no overlays.list file found"); + return; + } + + try { + // Acquire a lock so that any idmap scanning doesn't impact the current set. + // The order of this try-with-resources block matters. We must release the lock, and + // then close the file streams when exiting the block. + try (final BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + final FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE, true /*shared*/)) { + for (String line; (line = br.readLine()) != null; ) { + final String idmapPath = line.split(" ")[1]; + outApkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + } } + } finally { + // When BufferedReader is closed above, FileInputStream is closed as well. But let's be + // paranoid. + IoUtils.closeQuietly(fis); } - init(true); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); } /** * Return a global shared asset manager that provides access to only * system assets (no application assets). - * {@hide} + * @hide */ public static AssetManager getSystem() { - ensureSystemAssets(); - return sSystem; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + return sSystem; + } } /** * Close this asset manager. */ + @Override public void close() { - synchronized(this) { - //System.out.println("Release: num=" + mNumRefs - // + ", released=" + mReleased); + synchronized (this) { + if (!mOpen) { + return; + } + + mOpen = false; + decRefsLocked(hashCode()); + } + } + + /** + * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)} + * family of methods. + * + * @param apkAssets The new set of paths. + * @param invalidateCaches Whether to invalidate any caches. This should almost always be true. + * Set this to false if you are appending new resources + * (not new configurations). + * @hide + */ + public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) { + Preconditions.checkNotNull(apkAssets, "apkAssets"); + + // Copy the apkAssets, but prepend the system assets (framework + overlays). + final ApkAssets[] newApkAssets = new ApkAssets[apkAssets.length + sSystemApkAssets.length]; + System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length); + System.arraycopy(apkAssets, 0, newApkAssets, sSystemApkAssets.length, apkAssets.length); + + synchronized (this) { + ensureOpenLocked(); + mApkAssets = newApkAssets; + nativeSetApkAssets(mObject, mApkAssets, invalidateCaches); + if (invalidateCaches) { + // Invalidate all caches. + invalidateCachesLocked(-1); + } + } + } + + /** + * Invalidates the caches in this AssetManager according to the bitmask `diff`. + * + * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}. + * @see ActivityInfo.Config + */ + private void invalidateCachesLocked(int diff) { + // TODO(adamlesinski): Currently there are no caches to invalidate in Java code. + } + + /** + * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this + * returns a 0-length array. + * @hide + */ + public @NonNull ApkAssets[] getApkAssets() { + synchronized (this) { if (mOpen) { - mOpen = false; - decRefsLocked(this.hashCode()); + return mApkAssets; + } + } + return sEmptyApkAssets; + } + + /** + * Returns a cookie for use with the other APIs of AssetManager. + * @return 0 if the path was not found, otherwise a positive integer cookie representing + * this path in the AssetManager. + * @hide + */ + public int findCookieForPath(@NonNull String path) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (path.equals(mApkAssets[i].getAssetPath())) { + return i + 1; + } + } + } + return 0; + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPath(String path) { + return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPathAsSharedLibrary(String path) { + return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addOverlayPath(String path) { + return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); + } + + private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureOpenLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (mApkAssets[i].getAssetPath().equals(path)) { + return i + 1; + } } + + final ApkAssets assets; + try { + if (overlay) { + // TODO(b/70343104): This hardcoded path will be removed once + // addAssetPathInternal is deleted. + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib); + } + } catch (IOException e) { + return 0; + } + + final ApkAssets[] newApkAssets = Arrays.copyOf(mApkAssets, count + 1); + newApkAssets[count] = assets; + setApkAssets(newApkAssets, true); + return count + 1; + } + } + + /** + * Ensures that the native implementation has not been destroyed. + * The AssetManager may have been closed, but references to it still exist + * and therefore the native implementation is not destroyed. + */ + @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"); + } + } + + /** + * Populates {@code outValue} with the data associated a particular + * resource identifier for the current configuration. + * + * @param resId the resource identifier to load + * @param densityDpi the density bucket for which to load the resource + * @param outValue the typed value in which to put the data + * @param resolveRefs {@code true} to resolve references, {@code false} + * to leave them unresolved + * @return {@code true} if the data was loaded into {@code outValue}, + * {@code false} otherwise + */ + boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, + boolean resolveRefs) { + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeGetResourceValue( + mObject, resId, (short) densityDpi, outValue, resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; } } @@ -156,8 +427,7 @@ public final class AssetManager implements AutoCloseable { * @param resId the resource identifier to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceText(@StringRes int resId) { + @Nullable CharSequence getResourceText(@StringRes int resId) { synchronized (this) { final TypedValue outValue = mValue; if (getResourceValue(resId, 0, outValue, true)) { @@ -172,15 +442,15 @@ public final class AssetManager implements AutoCloseable { * identifier for the current configuration. * * @param resId the resource identifier to load - * @param bagEntryId + * @param bagEntryId the index into the bag to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { + @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { synchronized (this) { + ensureValidLocked(); final TypedValue outValue = mValue; - final int block = loadResourceBagValue(resId, bagEntryId, outValue, true); - if (block < 0) { + final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue); + if (cookie <= 0) { return null; } @@ -189,52 +459,60 @@ public final class AssetManager implements AutoCloseable { outValue.changingConfigurations); if (outValue.type == TypedValue.TYPE_STRING) { - return mStringBlocks[block].get(outValue.data); + return mApkAssets[cookie - 1].getStringFromPool(outValue.data); } return outValue.coerceToString(); } } + int getResourceArraySize(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArraySize(mObject, resId); + } + } + /** - * Retrieves the string array associated with a particular resource - * identifier for the current configuration. + * Populates `outData` with array elements of `resId`. `outData` is normally + * used with + * {@link TypedArray}. * - * @param resId the resource identifier of the string array - * @return the string array, or {@code null} + * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES} + * long, + * with the indices of the data representing the type, value, asset cookie, + * resource ID, + * configuration change mask, and density of the element. + * + * @param resId The resource ID of an array resource. + * @param outData The array to populate with data. + * @return The length of the array. + * + * @see TypedArray#STYLE_TYPE + * @see TypedArray#STYLE_DATA + * @see TypedArray#STYLE_ASSET_COOKIE + * @see TypedArray#STYLE_RESOURCE_ID + * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS + * @see TypedArray#STYLE_DENSITY */ - @Nullable - final String[] getResourceStringArray(@ArrayRes int resId) { - return getArrayStringResource(resId); + int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) { + Preconditions.checkNotNull(outData, "outData"); + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArray(mObject, resId, outData); + } } /** - * Populates {@code outValue} with the data associated a particular - * resource identifier for the current configuration. + * Retrieves the string array associated with a particular resource + * identifier for the current configuration. * - * @param resId the resource identifier to load - * @param densityDpi the density bucket for which to load the resource - * @param outValue the typed value in which to put the data - * @param resolveRefs {@code true} to resolve references, {@code false} - * to leave them unresolved - * @return {@code true} if the data was loaded into {@code outValue}, - * {@code false} otherwise + * @param resId the resource identifier of the string array + * @return the string array, or {@code null} */ - final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, - boolean resolveRefs) { + @Nullable String[] getResourceStringArray(@ArrayRes int resId) { synchronized (this) { - final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); - if (block < 0) { - return false; - } - - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); - - if (outValue.type == TypedValue.TYPE_STRING) { - outValue.string = mStringBlocks[block].get(outValue.data); - } - return true; + ensureValidLocked(); + return nativeGetResourceStringArray(mObject, resId); } } @@ -244,26 +522,48 @@ public final class AssetManager implements AutoCloseable { * * @param resId the resource id of the string array */ - final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { + @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { synchronized (this) { - final int[] rawInfoArray = getArrayStringInfo(resId); + ensureValidLocked(); + final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId); if (rawInfoArray == null) { return null; } + final int rawInfoArrayLen = rawInfoArray.length; final int infoArrayLen = rawInfoArrayLen / 2; - int block; - int index; final CharSequence[] retArray = new CharSequence[infoArrayLen]; for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) { - block = rawInfoArray[i]; - index = rawInfoArray[i + 1]; - retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null; + int cookie = rawInfoArray[i]; + int index = rawInfoArray[i + 1]; + retArray[j] = (index >= 0 && cookie > 0) + ? mApkAssets[cookie - 1].getStringFromPool(index) : null; } return retArray; } } + @Nullable int[] getResourceIntArray(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceIntArray(mObject, resId); + } + } + + /** + * Get the attributes for a style resource. These are the <item> + * elements in + * a <style> resource. + * @param resId The resource ID of the style + * @return An array of attribute IDs. + */ + @AttrRes int[] getStyleAttributes(@StyleRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetStyleAttributes(mObject, resId); + } + } + /** * Populates {@code outValue} with the data associated with a particular * resource identifier for the current configuration. Resolves theme @@ -277,73 +577,88 @@ public final class AssetManager implements AutoCloseable { * @return {@code true} if the data was loaded into {@code outValue}, * {@code false} otherwise */ - final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, + boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, boolean resolveRefs) { - final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs); - if (block < 0) { - return false; + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue, + resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; } + } - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); + void dumpTheme(long theme, int priority, String tag, String prefix) { + synchronized (this) { + ensureValidLocked(); + nativeThemeDump(mObject, theme, priority, tag, prefix); + } + } - if (outValue.type == TypedValue.TYPE_STRING) { - final StringBlock[] blocks = ensureStringBlocks(); - outValue.string = blocks[block].get(outValue.data); + @Nullable String getResourceName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceName(mObject, resId); } - return true; } - /** - * Ensures the string blocks are loaded. - * - * @return the string blocks - */ - @NonNull - final StringBlock[] ensureStringBlocks() { + @Nullable String getResourcePackageName(@AnyRes int resId) { synchronized (this) { - if (mStringBlocks == null) { - makeStringBlocks(sSystem.mStringBlocks); - } - return mStringBlocks; + ensureValidLocked(); + return nativeGetResourcePackageName(mObject, resId); } } - /*package*/ final void makeStringBlocks(StringBlock[] seed) { - final int seedNum = (seed != null) ? seed.length : 0; - final int num = getStringBlockCount(); - mStringBlocks = new StringBlock[num]; - if (localLOGV) Log.v(TAG, "Making string blocks for " + this - + ": " + num); - for (int i=0; i<num; i++) { - if (i < seedNum) { - mStringBlocks[i] = seed[i]; - } else { - mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); - } + @Nullable String getResourceTypeName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceTypeName(mObject, resId); + } + } + + @Nullable String getResourceEntryName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceEntryName(mObject, resId); } } - /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) { + @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType, + @Nullable String defPackage) { synchronized (this) { - // Cookies map to string blocks starting at 1. - return mStringBlocks[cookie - 1].get(id); + ensureValidLocked(); + // name is checked in JNI. + return nativeGetResourceIdentifier(mObject, name, defType, defPackage); } } + CharSequence getPooledStringForCookie(int cookie, int id) { + // Cookies map to ApkAssets starting at 1. + return getApkAssets()[cookie - 1].getStringFromPool(id); + } + /** * Open an asset using ACCESS_STREAMING mode. This provides access to * files that have been bundled with an application as assets -- that is, * files placed in to the "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * * @see #open(String, int) * @see #list */ - public final InputStream open(String fileName) throws IOException { + public @NonNull InputStream open(@NonNull String fileName) throws IOException { return open(fileName, ACCESS_STREAMING); } @@ -353,8 +668,7 @@ public final class AssetManager implements AutoCloseable { * with an application as assets -- that is, files placed in to the * "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * @param accessMode Desired access mode for retrieving the data. * * @see #ACCESS_UNKNOWN @@ -364,34 +678,40 @@ public final class AssetManager implements AutoCloseable { * @see #open(String) * @see #list */ - public final InputStream open(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openAsset(fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenAsset(mObject, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset file: " + fileName); } - public final AssetFileDescriptor openFd(String fileName) - throws IOException { + /** + * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides access to files that have been bundled with an application as assets -- that + * is, files placed in to the "assets" directory. + * + * The asset must be uncompressed, or an exception will be thrown. + * + * @param fileName The name of the asset to open. This name can be hierarchical. + * @return An open AssetFileDescriptor. + */ + public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset file: " + fileName); } /** @@ -406,90 +726,121 @@ public final class AssetManager implements AutoCloseable { * * @see #open */ - public native final String[] list(String path) - throws IOException; + public @Nullable String[] list(@NonNull String path) throws IOException { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + return nativeList(mObject, path); + } + } /** - * {@hide} * Open a non-asset file as an asset using ACCESS_STREAMING mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * * @see #open(String) + * @hide */ - public final InputStream openNonAsset(String fileName) throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException { return openNonAsset(0, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset file as an asset using a specific access mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * @param accessMode Desired access mode for retrieving the data. + * + * @see #ACCESS_UNKNOWN + * @see #ACCESS_STREAMING + * @see #ACCESS_RANDOM + * @see #ACCESS_BUFFER * @see #open(String, int) + * @hide */ - public final InputStream openNonAsset(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode) + throws IOException { return openNonAsset(0, fileName, accessMode); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName) + throws IOException { return openNonAsset(cookie, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. * @param accessMode Desired access mode for retrieving the data. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openNonAssetNative(cookie, fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset absolute file: " + fileName); } - public final AssetFileDescriptor openNonAssetFd(String fileName) + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName) throws IOException { return openNonAssetFd(0, fileName); } - - public final AssetFileDescriptor openNonAssetFd(int cookie, - String fileName) throws IOException { + + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param cookie Identifier of the package to be opened. + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openNonAssetFdNative(cookie, - fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = + nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset absolute file: " + fileName); } /** @@ -497,7 +848,7 @@ public final class AssetManager implements AutoCloseable { * * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(String fileName) + public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName) throws IOException { return openXmlResourceParser(0, fileName); } @@ -508,270 +859,265 @@ public final class AssetManager implements AutoCloseable { * @param cookie Identifier of the package to be opened. * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(int cookie, - String fileName) throws IOException { - XmlBlock block = openXmlBlockAsset(cookie, fileName); - XmlResourceParser rp = block.newParser(); - block.close(); - return rp; + public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName) + throws IOException { + try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) { + XmlResourceParser parser = block.newParser(); + // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with + // a valid native pointer, which makes newParser always return non-null. But let's + // be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } } /** - * {@hide} - * Retrieve a non-asset as a compiled XML file. Not for use by - * applications. + * Retrieve a non-asset as a compiled XML file. Not for use by applications. * * @param fileName The name of the file to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException { return openXmlBlockAsset(0, fileName); } /** - * {@hide} * Retrieve a non-asset as a compiled XML file. Not for use by * applications. * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long xmlBlock = openXmlAssetNative(cookie, fileName); - if (xmlBlock != 0) { - XmlBlock res = new XmlBlock(this, xmlBlock); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); + if (xmlBlock == 0) { + throw new FileNotFoundException("Asset XML file: " + fileName); } + final XmlBlock block = new XmlBlock(this, xmlBlock); + incRefsLocked(block.hashCode()); + return block; } - throw new FileNotFoundException("Asset XML file: " + fileName); } - /*package*/ void xmlBlockGone(int id) { + void xmlBlockGone(int id) { synchronized (this) { decRefsLocked(id); } } - /*package*/ final long createTheme() { + void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress, + long outIndicesAddress) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long res = newTheme(); - incRefsLocked(res); - return res; + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes, + parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress, + outIndicesAddress); + } + } + + boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues, + @NonNull int[] outIndices) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeResolveAttrs(mObject, + themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices); + } + } + + boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs, + @NonNull int[] outValues, @NonNull int[] outIndices) { + Preconditions.checkNotNull(parser, "parser"); + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeRetrieveAttributes( + mObject, parser.mParseState, inAttrs, outValues, outIndices); } } - /*package*/ final void releaseTheme(long theme) { + long createTheme() { synchronized (this) { - deleteTheme(theme); - decRefsLocked(theme); + ensureValidLocked(); + long themePtr = nativeThemeCreate(mObject); + incRefsLocked(themePtr); + return themePtr; } } + void releaseTheme(long themePtr) { + synchronized (this) { + nativeThemeDestroy(themePtr); + decRefsLocked(themePtr); + } + } + + void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) { + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeThemeApplyStyle(mObject, themePtr, resId, force); + } + } + + @Override protected void finalize() throws Throwable { - try { - if (DEBUG_REFS && mNumRefs != 0) { - Log.w(TAG, "AssetManager " + this - + " finalized with non-zero refs: " + mNumRefs); - if (mRefStacks != null) { - for (RuntimeException e : mRefStacks.values()) { - Log.w(TAG, "Reference from here", e); - } + if (DEBUG_REFS && mNumRefs != 0) { + Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs); + if (mRefStacks != null) { + for (RuntimeException e : mRefStacks.values()) { + Log.w(TAG, "Reference from here", e); } } - destroy(); - } finally { - super.finalize(); + } + + if (mObject != 0) { + nativeDestroy(mObject); } } - + + /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread + safe and it does not rely on AssetManager once it has been created. It completely owns the + underlying Asset. */ public final class AssetInputStream extends InputStream { + private long mAssetNativePtr; + private long mLength; + private long mMarkPos; + /** * @hide */ public final int getAssetInt() { throw new UnsupportedOperationException(); } + /** * @hide */ public final long getNativeAsset() { - return mAsset; + return mAssetNativePtr; } - private AssetInputStream(long asset) - { - mAsset = asset; - mLength = getAssetLength(asset); + + private AssetInputStream(long assetNativePtr) { + mAssetNativePtr = assetNativePtr; + mLength = nativeAssetGetLength(assetNativePtr); } + + @Override public final int read() throws IOException { - return readAssetChar(mAsset); - } - public final boolean markSupported() { - return true; - } - public final int available() throws IOException { - long len = getAssetRemainingLength(mAsset); - return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; + ensureOpen(); + return nativeAssetReadChar(mAssetNativePtr); } - public final void close() throws IOException { - synchronized (AssetManager.this) { - if (mAsset != 0) { - destroyAsset(mAsset); - mAsset = 0; - decRefsLocked(hashCode()); - } - } - } - public final void mark(int readlimit) { - mMarkPos = seekAsset(mAsset, 0, 0); - } - public final void reset() throws IOException { - seekAsset(mAsset, mMarkPos, -1); - } - public final int read(byte[] b) throws IOException { - return readAsset(mAsset, b, 0, b.length); + + @Override + public final int read(@NonNull byte[] b) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, 0, b.length); } - public final int read(byte[] b, int off, int len) throws IOException { - return readAsset(mAsset, b, off, len); + + @Override + public final int read(@NonNull byte[] b, int off, int len) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, off, len); } + + @Override public final long skip(long n) throws IOException { - long pos = seekAsset(mAsset, 0, 0); - if ((pos+n) > mLength) { - n = mLength-pos; + ensureOpen(); + long pos = nativeAssetSeek(mAssetNativePtr, 0, 0); + if ((pos + n) > mLength) { + n = mLength - pos; } if (n > 0) { - seekAsset(mAsset, n, 0); + nativeAssetSeek(mAssetNativePtr, n, 0); } return n; } - protected void finalize() throws Throwable - { - close(); + @Override + public final int available() throws IOException { + ensureOpen(); + final long len = nativeAssetGetRemainingLength(mAssetNativePtr); + return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len; } - private long mAsset; - private long mLength; - private long mMarkPos; - } - - /** - * Add an additional set of assets to the asset manager. This can be - * either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPath(String path) { - return addAssetPathInternal(path, false); - } - - /** - * Add an application assets to the asset manager and loading it as shared library. - * This can be either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPathAsSharedLibrary(String path) { - return addAssetPathInternal(path, true); - } - - private final int addAssetPathInternal(String path, boolean appAsLib) { - synchronized (this) { - int res = addAssetPathNative(path, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final boolean markSupported() { + return true; } - } - - private native final int addAssetPathNative(String path, boolean appAsLib); - /** - * Add an additional set of assets to the asset manager from an already open - * FileDescriptor. Not for use by applications. - * This does not give full AssetManager functionality for these assets, - * since the origin of the file is not known for purposes of sharing, - * overlay resolution, and other features. However it does allow you - * to do simple access to the contents of the given fd as an apk file. - * Performs a dup of the underlying fd, so you must take care of still closing - * the FileDescriptor yourself (and can do that whenever you want). - * Returns the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public int addAssetFd(FileDescriptor fd, String debugPathName) { - return addAssetFdInternal(fd, debugPathName, false); - } - - private int addAssetFdInternal(FileDescriptor fd, String debugPathName, - boolean appAsLib) { - synchronized (this) { - int res = addAssetFdNative(fd, debugPathName, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void mark(int readlimit) { + ensureOpen(); + mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0); } - } - - private native int addAssetFdNative(FileDescriptor fd, String debugPathName, - boolean appAsLib); - - /** - * Add a set of assets to overlay an already added set of assets. - * - * This is only intended for application resources. System wide resources - * are handled before any Java code is executed. - * - * {@hide} - */ - public final int addOverlayPath(String idmapPath) { - synchronized (this) { - int res = addOverlayPathNative(idmapPath); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void reset() throws IOException { + ensureOpen(); + nativeAssetSeek(mAssetNativePtr, mMarkPos, -1); } - } - /** - * See addOverlayPath. - * - * {@hide} - */ - public native final int addOverlayPathNative(String idmapPath); + @Override + public final void close() throws IOException { + if (mAssetNativePtr != 0) { + nativeAssetDestroy(mAssetNativePtr); + mAssetNativePtr = 0; - /** - * Add multiple sets of assets to the asset manager at once. See - * {@link #addAssetPath(String)} for more information. Returns array of - * cookies for each added asset with 0 indicating failure, or null if - * the input array of paths is null. - * {@hide} - */ - public final int[] addAssetPaths(String[] paths) { - if (paths == null) { - return null; + synchronized (AssetManager.this) { + decRefsLocked(hashCode()); + } + } } - int[] cookies = new int[paths.length]; - for (int i = 0; i < paths.length; i++) { - cookies[i] = addAssetPath(paths[i]); + @Override + protected void finalize() throws Throwable { + close(); } - return cookies; + private void ensureOpen() { + if (mAssetNativePtr == 0) { + throw new IllegalStateException("AssetInputStream is closed"); + } + } } /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to * instantiate a new AssetManager class to see the new data. - * {@hide} + * @hide */ - public native final boolean isUpToDate(); + public boolean isUpToDate() { + for (ApkAssets apkAssets : getApkAssets()) { + if (!apkAssets.isUpToDate()) { + return false; + } + } + return true; + } /** * Get the locales that this asset manager contains data for. @@ -784,7 +1130,12 @@ public final class AssetManager implements AutoCloseable { * are of the form {@code ll_CC} where {@code ll} is a two letter language code, * and {@code CC} is a two letter country code. */ - public native final String[] getLocales(); + public String[] getLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, false /*excludeSystem*/); + } + } /** * Same as getLocales(), except that locales that are only provided by the system (i.e. those @@ -794,131 +1145,58 @@ public final class AssetManager implements AutoCloseable { * assets support Cherokee and French, getLocales() would return * [Cherokee, English, French, German], while getNonSystemLocales() would return * [Cherokee, French]. - * {@hide} + * @hide */ - public native final String[] getNonSystemLocales(); - - /** {@hide} */ - public native final Configuration[] getSizeConfigurations(); + public String[] getNonSystemLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, true /*excludeSystem*/); + } + } /** - * Change the configuation used when retrieving resources. Not for use by - * applications. - * {@hide} + * @hide */ - public native final void setConfiguration(int mcc, int mnc, String locale, - int orientation, int touchscreen, int density, int keyboard, - int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, - int screenLayout, int uiMode, int colorMode, int majorVersion); + Configuration[] getSizeConfigurations() { + synchronized (this) { + ensureValidLocked(); + return nativeGetSizeConfigurations(mObject); + } + } /** - * Retrieve the resource identifier for the given resource name. + * Change the configuration used when retrieving resources. Not for use by + * applications. + * @hide */ - /*package*/ native final int getResourceIdentifier(String name, - String defType, - String defPackage); + public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation, + int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, + int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, + int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) { + synchronized (this) { + ensureValidLocked(); + nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density, + keyboard, keyboardHidden, navigation, screenWidth, screenHeight, + smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode, + colorMode, majorVersion); + } + } - /*package*/ native final String getResourceName(int resid); - /*package*/ native final String getResourcePackageName(int resid); - /*package*/ native final String getResourceTypeName(int resid); - /*package*/ native final String getResourceEntryName(int resid); - - private native final long openAsset(String fileName, int accessMode); - private final native ParcelFileDescriptor openAssetFd(String fileName, - long[] outOffsets) throws IOException; - private native final long openNonAssetNative(int cookie, String fileName, - int accessMode); - private native ParcelFileDescriptor openNonAssetFdNative(int cookie, - String fileName, long[] outOffsets) throws IOException; - private native final void destroyAsset(long asset); - private native final int readAssetChar(long asset); - private native final int readAsset(long asset, byte[] b, int off, int len); - private native final long seekAsset(long asset, long offset, int whence); - private native final long getAssetLength(long asset); - private native final long getAssetRemainingLength(long asset); - - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceValue(int ident, short density, TypedValue outValue, - boolean resolve); - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, - boolean resolve); - /*package*/ static final int STYLE_NUM_ENTRIES = 6; - /*package*/ static final int STYLE_TYPE = 0; - /*package*/ static final int STYLE_DATA = 1; - /*package*/ static final int STYLE_ASSET_COOKIE = 2; - /*package*/ static final int STYLE_RESOURCE_ID = 3; - - /* Offset within typed data array for native changingConfigurations. */ - static final int STYLE_CHANGING_CONFIGURATIONS = 4; - - /*package*/ static final int STYLE_DENSITY = 5; - /*package*/ native static final void applyStyle(long theme, - int defStyleAttr, int defStyleRes, long xmlParser, - int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress); - /*package*/ native static final boolean resolveAttrs(long theme, - int defStyleAttr, int defStyleRes, int[] inValues, - int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final boolean retrieveAttributes( - long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final int getArraySize(int resource); - /*package*/ native final int retrieveArray(int resource, int[] outValues); - private native final int getStringBlockCount(); - private native final long getNativeStringBlock(int block); - - /** - * {@hide} - */ - public native final String getCookieName(int cookie); - - /** - * {@hide} - */ - public native final SparseArray<String> getAssignedPackageIdentifiers(); - - /** - * {@hide} - */ - public native static final int getGlobalAssetCount(); - /** - * {@hide} + * @hide */ - public native static final String getAssetAllocations(); - - /** - * {@hide} - */ - public native static final int getGlobalAssetManagerCount(); - - private native final long newTheme(); - private native final void deleteTheme(long theme); - /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force); - /*package*/ native static final void copyTheme(long dest, long source); - /*package*/ native static final void clearTheme(long theme); - /*package*/ native static final int loadThemeAttributeValue(long theme, int ident, - TypedValue outValue, - boolean resolve); - /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix); - /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme); - - private native final long openXmlAssetNative(int cookie, String fileName); - - private native final String[] getArrayStringResource(int arrayRes); - private native final int[] getArrayStringInfo(int arrayRes); - /*package*/ native final int[] getArrayIntResource(int arrayRes); - /*package*/ native final int[] getStyleAttributes(int themeRes); - - private native final void init(boolean isSystem); - private native final void destroy(); - - private final void incRefsLocked(long id) { + public SparseArray<String> getAssignedPackageIdentifiers() { + synchronized (this) { + ensureValidLocked(); + return nativeGetAssignedPackageIdentifiers(mObject); + } + } + + @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 @@ public final class AssetManager implements AutoCloseable { } 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 e173653cd961..d8133824f757 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -590,7 +590,7 @@ public class Resources { */ @NonNull public int[] getIntArray(@ArrayRes int id) throws NotFoundException { - int[] res = mResourcesImpl.getAssets().getArrayIntResource(id); + int[] res = mResourcesImpl.getAssets().getResourceIntArray(id); if (res != null) { return res; } @@ -613,13 +613,13 @@ public class Resources { @NonNull public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException { final ResourcesImpl impl = mResourcesImpl; - int len = impl.getAssets().getArraySize(id); + int len = impl.getAssets().getResourceArraySize(id); if (len < 0) { throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); } TypedArray array = TypedArray.obtain(this, len); - array.mLength = impl.getAssets().retrieveArray(id, array.mData); + array.mLength = impl.getAssets().getResourceArray(id, array.mData); array.mIndices[0] = 0; return array; @@ -867,8 +867,9 @@ public class Resources { * @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 @@ public class Resources { * 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 @@ public class Resources { * @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 @@ public class Resources { } } - @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 @@ public class Resources { /** * @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 @@ public class Resources { * * @see #getXml */ + @NonNull public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException { return loadXmlResourceParser(id, "layout"); } @@ -1163,6 +1165,7 @@ public class Resources { * * @see #getXml */ + @NonNull public XmlResourceParser getAnimation(@AnimatorRes @AnimRes int id) throws NotFoundException { return loadXmlResourceParser(id, "anim"); } @@ -1188,6 +1191,7 @@ public class Resources { * * @see android.util.AttributeSet */ + @NonNull public XmlResourceParser getXml(@XmlRes int id) throws NotFoundException { return loadXmlResourceParser(id, "xml"); } @@ -1203,8 +1207,8 @@ public class Resources { * @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 @@ public class Resources { * * @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 @@ public class Resources { // out the attributes from the XML file (applying type information // contained in the resources and such). XmlBlock.Parser parser = (XmlBlock.Parser)set; - mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs, - array.mData, array.mIndices); + mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices); array.mXml = parser; diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 97cb78bc4243..08a161347e59 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -168,7 +168,6 @@ public class ResourcesImpl { mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo()); - mAssets.ensureStringBlocks(); } public DisplayAdjustments getDisplayAdjustments() { @@ -543,7 +542,7 @@ public class ResourcesImpl { } } - @Nullable + @NonNull Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme) throws NotFoundException { @@ -627,7 +626,7 @@ public class ResourcesImpl { } 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 @@ public class ResourcesImpl { /** * 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 @@ public class ResourcesImpl { } } - // 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 @@ public class ResourcesImpl { 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 @@ public class ResourcesImpl { final long loadedBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize; final int loadedDrawables = - sPreloadTracingNumLoadedDrawables - startDrwableCount; + sPreloadTracingNumLoadedDrawables - startDrawableCount; sPreloadTracingNumLoadedDrawables++; @@ -910,6 +911,7 @@ public class ResourcesImpl { * 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 @@ public class ResourcesImpl { 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 @@ public class ResourcesImpl { return complexColor; } - @Nullable + @NonNull ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id, Resources.Theme theme) throws NotFoundException { @@ -1045,7 +1045,7 @@ public class ResourcesImpl { * * @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 @@ public class ResourcesImpl { void applyStyle(int resId, boolean force) { synchronized (mKey) { - AssetManager.applyThemeStyle(mTheme, resId, force); - + mAssets.applyStyleToTheme(mTheme, resId, force); mThemeResId = resId; mKey.append(resId, force); } @@ -1284,7 +1283,7 @@ public class ResourcesImpl { void setTo(ThemeImpl other) { synchronized (mKey) { synchronized (other.mKey) { - AssetManager.copyTheme(mTheme, other.mTheme); + AssetManager.nativeThemeCopy(mTheme, other.mTheme); mThemeResId = other.mThemeResId; mKey.setTo(other.getKey()); @@ -1307,12 +1306,10 @@ public class ResourcesImpl { // out the attributes from the XML file (applying type information // contained in the resources and such). final XmlBlock.Parser parser = (XmlBlock.Parser) set; - AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, - parser != null ? parser.mParseState : 0, - attrs, attrs.length, array.mDataAddress, array.mIndicesAddress); + mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs, + array.mDataAddress, array.mIndicesAddress); array.mTheme = wrapper; array.mXml = parser; - return array; } } @@ -1329,7 +1326,7 @@ public class ResourcesImpl { } final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); - AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); + mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); array.mTheme = wrapper; array.mXml = null; return array; @@ -1349,14 +1346,14 @@ public class ResourcesImpl { @Config int getChangingConfigurations() { synchronized (mKey) { final @NativeConfig int nativeChangingConfig = - AssetManager.getThemeChangingConfigurations(mTheme); + AssetManager.nativeThemeGetChangingConfigurations(mTheme); return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); } } public void dump(int priority, String tag, String prefix) { synchronized (mKey) { - AssetManager.dumpTheme(mTheme, priority, tag, prefix); + mAssets.dumpTheme(mTheme, priority, tag, prefix); } } @@ -1385,13 +1382,13 @@ public class ResourcesImpl { */ void rebase() { synchronized (mKey) { - AssetManager.clearTheme(mTheme); + AssetManager.nativeThemeClear(mTheme); // Reapply the same styles in the same order. for (int i = 0; i < mKey.mCount; i++) { final int resId = mKey.mResId[i]; final boolean force = mKey.mForce[i]; - AssetManager.applyThemeStyle(mTheme, resId, force); + mAssets.applyStyleToTheme(mTheme, resId, force); } } } diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index f33c75168a5f..cbb3c6df0558 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -61,6 +61,15 @@ public class TypedArray { return attrs; } + // STYLE_ prefixed constants are offsets within the typed data array. + static final int STYLE_NUM_ENTRIES = 6; + static final int STYLE_TYPE = 0; + static final int STYLE_DATA = 1; + static final int STYLE_ASSET_COOKIE = 2; + static final int STYLE_RESOURCE_ID = 3; + static final int STYLE_CHANGING_CONFIGURATIONS = 4; + static final int STYLE_DENSITY = 5; + private final Resources mResources; private DisplayMetrics mMetrics; private AssetManager mAssets; @@ -78,7 +87,7 @@ public class TypedArray { private void resize(int len) { mLength = len; - final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES; + final int dataLen = len * STYLE_NUM_ENTRIES; final int indicesLen = len + 1; final VMRuntime runtime = VMRuntime.getRuntime(); if (mDataAddress == 0 || mData.length < dataLen) { @@ -166,9 +175,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -203,9 +212,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -242,14 +251,13 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_STRING) { - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]).toString(); + return mXml.getPooledString(data[index + STYLE_DATA]).toString(); } } return null; @@ -274,11 +282,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); if ((changingConfigs & ~allowedChangingConfigs) != 0) { return null; } @@ -320,14 +328,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA] != 0; + return data[index + STYLE_DATA] != 0; } final TypedValue v = mValue; @@ -359,14 +367,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -396,16 +404,16 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FLOAT) { - return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); + return Float.intBitsToFloat(data[index + STYLE_DATA]); } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -446,15 +454,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; if (getValueAt(index, value)) { @@ -498,7 +506,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -533,7 +541,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -564,15 +572,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -612,15 +620,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimension( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -661,15 +668,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelOffset( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -711,15 +717,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -755,16 +760,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -795,15 +799,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } return defValue; @@ -833,15 +836,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FRACTION) { - return TypedValue.complexToFraction( - data[index+AssetManager.STYLE_DATA], base, pbase); + return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -874,10 +876,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) { - final int resid = data[index+AssetManager.STYLE_RESOURCE_ID]; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) { + final int resid = data[index + STYLE_RESOURCE_ID]; if (resid != 0) { return resid; } @@ -902,10 +904,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { - return data[index + AssetManager.STYLE_DATA]; + if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { + return data[index + STYLE_DATA]; } return defValue; } @@ -939,7 +941,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -975,7 +977,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -1006,7 +1008,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return mResources.getTextArray(value.resourceId); } return null; @@ -1027,7 +1029,7 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue); + return getValueAt(index * STYLE_NUM_ENTRIES, outValue); } /** @@ -1043,8 +1045,8 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; - return mData[index + AssetManager.STYLE_TYPE]; + index *= STYLE_NUM_ENTRIES; + return mData[index + STYLE_TYPE]; } /** @@ -1063,9 +1065,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL; } @@ -1084,11 +1086,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL - || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; + || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; } /** @@ -1109,7 +1111,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return value; } return null; @@ -1181,16 +1183,16 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { + final int index = i * STYLE_NUM_ENTRIES; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { // Not an attribute, ignore. continue; } // Null the entry so that we can safely call getZzz(). - data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL; + data[index + STYLE_TYPE] = TypedValue.TYPE_NULL; - final int attr = data[index + AssetManager.STYLE_DATA]; + final int attr = data[index + STYLE_DATA]; if (attr == 0) { // Useless data, ignore. continue; @@ -1231,45 +1233,44 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - final int type = data[index + AssetManager.STYLE_TYPE]; + final int index = i * STYLE_NUM_ENTRIES; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { continue; } changingConfig |= ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); } return changingConfig; } private boolean getValueAt(int index, TypedValue outValue) { final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return false; } outValue.type = type; - outValue.data = data[index+AssetManager.STYLE_DATA]; - outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; - outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID]; + outValue.data = data[index + STYLE_DATA]; + outValue.assetCookie = data[index + STYLE_ASSET_COOKIE]; + outValue.resourceId = data[index + STYLE_RESOURCE_ID]; outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); - outValue.density = data[index+AssetManager.STYLE_DENSITY]; + data[index + STYLE_CHANGING_CONFIGURATIONS]); + outValue.density = data[index + STYLE_DENSITY]; outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null; return true; } private CharSequence loadStringValueAt(int index) { final int[] data = mData; - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { if (mXml != null) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]); + return mXml.getPooledString(data[index + STYLE_DATA]); } return null; } - return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]); + return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]); } /** @hide */ diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index e6b957414ea8..d4ccffb83ca5 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -16,6 +16,7 @@ package android.content.res; +import android.annotation.Nullable; import android.util.TypedValue; import com.android.internal.util.XmlUtils; @@ -33,7 +34,7 @@ import java.io.Reader; * * {@hide} */ -final class XmlBlock { +final class XmlBlock implements AutoCloseable { private static final boolean DEBUG=false; public XmlBlock(byte[] data) { @@ -48,6 +49,7 @@ final class XmlBlock { mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } + @Override public void close() { synchronized (this) { if (mOpen) { @@ -478,13 +480,13 @@ final class XmlBlock { * are doing! The given native object must exist for the entire lifetime * of this newly creating XmlBlock. */ - XmlBlock(AssetManager assets, long xmlBlock) { + XmlBlock(@Nullable AssetManager assets, long xmlBlock) { mAssets = assets; mNative = xmlBlock; mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false); } - private final AssetManager mAssets; + private @Nullable final AssetManager mAssets; private final long mNative; /*package*/ final StringBlock mStrings; private boolean mOpen = true; diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index b211700328b9..dc60612451d4 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -422,6 +422,7 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + @GuardedBy("mLock") private boolean recycleConnectionLocked(SQLiteConnection connection, AcquiredConnectionStatus status) { if (status == AcquiredConnectionStatus.RECONFIGURE) { @@ -531,6 +532,7 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + @GuardedBy("mLock") private void closeAvailableConnectionsAndLogExceptionsLocked() { closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); @@ -541,6 +543,7 @@ public final class SQLiteConnectionPool implements Closeable { } // 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 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + @GuardedBy("mLock") private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() { final int count = mAvailableNonPrimaryConnections.size(); for (int i = 0; i < count; i++) { @@ -581,6 +585,7 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + @GuardedBy("mLock") private void closeExcessConnectionsAndLogExceptionsLocked() { int availableCount = mAvailableNonPrimaryConnections.size(); while (availableCount-- > mMaxConnectionPoolSize - 1) { @@ -591,6 +596,7 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + @GuardedBy("mLock") private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) { try { connection.close(); // might throw @@ -609,6 +615,7 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + @GuardedBy("mLock") private void reconfigureAllConnectionsLocked() { if (mAvailablePrimaryConnection != null) { try { @@ -776,6 +783,7 @@ public final class SQLiteConnectionPool implements Closeable { } // 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 @@ public final class SQLiteConnectionPool implements Closeable { } // 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 @@ public final class SQLiteConnectionPool implements Closeable { } // 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 @@ public final class SQLiteConnectionPool implements Closeable { } // Might throw. + @GuardedBy("mLock") private SQLiteConnection tryAcquireNonPrimaryConnectionLocked( String sql, int connectionFlags) { // Try to acquire the next connection in the queue. @@ -974,6 +985,7 @@ public final class SQLiteConnectionPool implements Closeable { } // 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 c1c0812e129e..ae1f57d62228 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -2006,7 +2006,6 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory, * SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING, * myDatabaseErrorHandler); - * db.enableWriteAheadLogging(); * </pre></code> * </p><p> * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index a817f33ce7ba..017674f5ea8a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1818,9 +1818,9 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * 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 287bdc88dd3e..74d64704c8d2 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -26,6 +26,7 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Random; @@ -329,16 +330,34 @@ public final class MacAddress implements Parcelable { /** * Returns a generated MAC address whose 24 least significant bits constituting the - * NIC part of the address are randomly selected. + * NIC part of the address are randomly selected and has Google OUI base. * * The locally assigned bit is always set to 1. The multicast bit is always set to 0. * - * @return a random locally assigned MacAddress. + * @return a random locally assigned, unicast MacAddress with Google OUI. + * + * @hide + */ + public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() { + return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom()); + } + + /** + * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the + * unicast bit, are randomly selected. + * + * The locally assigned bit is always set to 1. The multicast bit is always set to 0. + * + * @return a random locally assigned, unicast MacAddress. * * @hide */ public static @NonNull MacAddress createRandomUnicastAddress() { - return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random()); + SecureRandom r = new SecureRandom(); + long addr = r.nextLong() & VALID_LONG_MASK; + addr |= LOCALLY_ASSIGNED_MASK; + addr &= ~MULTICAST_MASK; + return new MacAddress(addr); } /** @@ -355,8 +374,8 @@ public final class MacAddress implements Parcelable { */ public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); - addr = addr | LOCALLY_ASSIGNED_MASK; - addr = addr & ~MULTICAST_MASK; + addr |= LOCALLY_ASSIGNED_MASK; + addr &= ~MULTICAST_MASK; return new MacAddress(addr); } diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index a734719afa5d..8b4f02efd153 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -323,4 +323,16 @@ public class BatteryManager { 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 fd0e5ae51892..5d7cf1e3899f 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1537,6 +1537,7 @@ public abstract class BatteryStats implements Parcelable { 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 @@ public abstract class BatteryStats implements Parcelable { 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/Build.java b/core/java/android/os/Build.java index 48f56847e88d..fc7886191898 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -894,6 +894,14 @@ public class Build { /** * 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 a565dee5ddd0..fbdf27e38d67 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -21,12 +21,6 @@ import android.annotation.SystemApi; /** @hide */ @SystemApi public interface IHwBinder { - // These MUST match their corresponding libhwbinder/IBinder.h definition !!! - /** @hide */ - public static final int FIRST_CALL_TRANSACTION = 1; - /** @hide */ - public static final int FLAG_ONEWAY = 1; - /** * Process a hwbinder transaction. * diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index 8a27700edc15..eae52171ee48 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -55,9 +55,8 @@ interface IStatsCompanionService { /** 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 679b49dfb974..682a24f17648 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -81,12 +81,26 @@ interface IStatsManager { /** * 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/VintfObject.java b/core/java/android/os/VintfObject.java index 12a495bf2821..fb22194098b6 100644 --- a/core/java/android/os/VintfObject.java +++ b/core/java/android/os/VintfObject.java @@ -80,4 +80,11 @@ public class VintfObject { * ("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/provider/Settings.java b/core/java/android/provider/Settings.java index e3816933aa30..84996e03a641 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7396,7 +7396,8 @@ public final class Settings { */ public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode"; - private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = BOOLEAN_VALIDATOR; + private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = + new SettingsValidators.InclusiveIntegerRangeValidator(0, 2); /** * Control the color temperature of Night Display, represented in Kelvin. @@ -10315,16 +10316,6 @@ public final class Settings { public static final String SYS_VDSO = "sys_vdso"; /** - * Uid CpuPower global setting. This links the uid.cpupower system property. - * The following values are supported: - * 0 -> /proc/uid_cpupower/* are disabled - * 1 -> /proc/uid_cpupower/* are enabled - * Any other value defaults to enabled. - * @hide - */ - public static final String UID_CPUPOWER = "uid_cpupower"; - - /** * An integer to reduce the FPS by this factor. Only for experiments. Need to reboot the * device for this setting to take full effect. * @@ -10333,6 +10324,15 @@ public final class Settings { public static final String FPS_DEVISOR = "fps_divisor"; /** + * Flag to enable or disable display panel low power mode (lpm) + * false -> Display panel power saving mode is disabled. + * true -> Display panel power saving mode is enabled. + * + * @hide + */ + public static final String DISPLAY_PANEL_LPM = "display_panel_lpm"; + + /** * App standby (app idle) specific settings. * This is encoded as a key=value list, separated by commas. Ex: * <p> diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index b7b2b2de35e5..422e36baee70 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1343,6 +1343,7 @@ public abstract class NotificationListenerService extends Service { /** * @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 0f85e1f9c5d9..9f2182eca908 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 da1483499220..172c8376c8cc 100644 --- a/core/java/android/transition/ArcMotion.java +++ b/core/java/android/transition/ArcMotion.java @@ -216,7 +216,13 @@ public class ArcMotion extends PathMotion { 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 @@ public class ArcMotion extends PathMotion { 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/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 410cdc6a9bf1..1ead0b49bbed 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -37,13 +37,13 @@ public class FeatureFlagUtils { 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 51fb18a95c4c..000000000000 --- 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 37e9815c93c5..7251b71ac35d 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_HEIGHT; 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 @@ public final class DisplayInfo implements Parcelable { 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 ea6226b3ea69..69973e6d367a 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 @@ import android.view.IRecentsAnimationController; 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 c28c3894482d..facf575872ed 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -79,6 +79,11 @@ public class RemoteAnimationTarget implements Parcelable { 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 class RemoteAnimationTarget implements Parcelable { 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 @@ public class RemoteAnimationTarget implements Parcelable { 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 @@ public class RemoteAnimationTarget implements Parcelable { 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 79fc13424bee..5de25bae6a3e 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P; + sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P; + sCompatibilityDone = true; } } @@ -8378,7 +8389,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityNodeProvider provider, AccessibilityNodeInfo info, boolean forAutofill) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), - null, null, null); + null, null, info.getViewIdResourceName()); Rect rect = structure.getTempRect(); info.getBoundsInParent(rect); structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height()); @@ -8418,6 +8429,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, CharSequence cname = info.getClassName(); structure.setClassName(cname != null ? cname.toString() : null); structure.setContentDescription(info.getContentDescription()); + if (forAutofill) { + final int maxTextLength = info.getMaxTextLength(); + if (maxTextLength != -1) { + structure.setMaxTextLength(maxTextLength); + } + structure.setHint(info.getHintText()); + } if ((info.getText() != null || info.getError() != null)) { structure.setText(info.getText(), info.getTextSelectionStart(), info.getTextSelectionEnd()); @@ -8428,7 +8446,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final AutofillValue autofillValue = AutofillValue.forText(structure.getText()); structure.setAutofillValue(autofillValue); if (info.isPassword()) { - structure.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); + structure.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); } } else { structure.setDataIsSensitive(false); @@ -23619,8 +23638,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // 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 273f097edc63..8e60a720f729 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3971,15 +3971,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Layout debugging code which draws rectangles around layout params. - * - * <p>This function is called automatically when the developer setting is enabled.<p/> - * - * <p>It is strongly advised to only call this function from debug builds as there is - * a risk of leaking unwanted layout information.<p/> - * - * @param canvas the canvas on which to draw - * @param paint the paint used to draw through + * @hide */ protected void onDebugDrawMargins(Canvas canvas, Paint paint) { for (int i = 0; i < getChildCount(); i++) { @@ -3989,19 +3981,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Layout debugging code which draws rectangles around: - * <ul> - * <li>optical bounds<li/> - * <li>margins<li/> - * <li>clip bounds<li/> - * <ul/> - * - * <p>This function is called automatically when the developer setting is enabled.<p/> - * - * <p>It is strongly advised to only call this function from debug builds as there is - * a risk of leaking unwanted layout information.<p/> - * - * @param canvas the canvas on which to draw + * @hide */ protected void onDebugDraw(Canvas canvas) { Paint paint = getDebugPaint(); @@ -7732,14 +7712,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters. * - * <p>This function is called automatically when the developer setting is enabled.<p/> - * - * <p>It is strongly advised to only call this function from debug builds as there is - * a risk of leaking unwanted layout information.<p/> - * * @param view the view that contains these layout parameters * @param canvas the canvas on which to draw - * @param paint the paint used to draw through + * + * @hide */ public void onDebugDraw(View view, Canvas canvas, Paint paint) { } @@ -8243,6 +8219,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL); } + /** + * @hide + */ @Override public void onDebugDraw(View view, Canvas canvas, Paint paint) { Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 23e7d6191276..417a72530b72 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3173,6 +3173,15 @@ public class AccessibilityNodeInfo implements Parcelable { */ @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 @@ public class AccessibilityNodeInfo implements Parcelable { + " 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 @@ public class AccessibilityNodeInfo implements Parcelable { 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 63a9990cf839..8b64bad8fe62 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -16,6 +16,10 @@ package android.view.autofill; +import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; +import static android.view.autofill.Helper.sDebug; +import static android.view.autofill.Helper.sVerbose; + import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.IntDef; import android.annotation.NonNull; @@ -47,13 +51,14 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; + import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; + import org.xmlpull.v1.XmlPullParserException; -import sun.misc.Cleaner; import java.io.IOException; import java.io.PrintWriter; @@ -66,9 +71,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; -import static android.view.autofill.Helper.sDebug; -import static android.view.autofill.Helper.sVerbose; +import sun.misc.Cleaner; // TODO: use java.lang.ref.Cleaner once Android supports Java 9 @@ -616,10 +619,9 @@ public final class AutofillManager { /** * @hide */ - public boolean isCompatibilityModeEnabled() { - synchronized (mLock) { - return mCompatibilityBridge != null; - } + @GuardedBy("mLock") + public boolean isCompatibilityModeEnabledLocked() { + return mCompatibilityBridge != null; } /** @@ -708,6 +710,7 @@ public final class AutofillManager { notifyViewEntered(view, 0); } + @GuardedBy("mLock") private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) { if (isDisabledByServiceLocked()) { if (sVerbose) { @@ -748,6 +751,7 @@ public final class AutofillManager { } /** 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 @@ public final class AutofillManager { } } + @GuardedBy("mLock") void notifyViewExitedLocked(@NonNull View view) { ensureServiceClientAddedIfNeededLocked(); @@ -892,6 +897,7 @@ public final class AutofillManager { } /** 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 @@ public final class AutofillManager { } } + @GuardedBy("mLock") private void notifyViewExitedLocked(@NonNull View view, int virtualId) { ensureServiceClientAddedIfNeededLocked(); @@ -1086,6 +1093,7 @@ public final class AutofillManager { } } + @GuardedBy("mLock") private void commitLocked() { if (!mEnabled && !isActiveLocked()) { return; @@ -1114,6 +1122,7 @@ public final class AutofillManager { } } + @GuardedBy("mLock") private void cancelLocked() { if (!mEnabled && !isActiveLocked()) { return; @@ -1377,11 +1386,13 @@ public final class AutofillManager { 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 @@ public final class AutofillManager { } try { final AutofillClient client = getClient(); - if (client == null) return; // NOTE: getClient() already logd it.. + if (client == null) return; // NOTE: getClient() already logged it.. mSessionId = mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), @@ -1406,6 +1417,7 @@ public final class AutofillManager { } } + @GuardedBy("mLock") private void finishSessionLocked() { if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked()); @@ -1420,6 +1432,7 @@ public final class AutofillManager { resetSessionLocked(); } + @GuardedBy("mLock") private void cancelSessionLocked() { if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked()); @@ -1434,6 +1447,7 @@ public final class AutofillManager { resetSessionLocked(); } + @GuardedBy("mLock") private void resetSessionLocked() { mSessionId = NO_SESSION; mState = STATE_UNKNOWN; @@ -1442,6 +1456,7 @@ public final class AutofillManager { 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 @@ public final class AutofillManager { } } + @GuardedBy("mLock") private void ensureServiceClientAddedIfNeededLocked() { if (getClient() == null) { return; @@ -1939,11 +1955,13 @@ public final class AutofillManager { pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); - pw.print(pfx); pw.print("compat mode enabled: "); pw.println(isCompatibilityModeEnabled()); + pw.print(pfx); pw.print("compat mode enabled: "); pw.println( + isCompatibilityModeEnabledLocked()); pw.print(pfx); pw.print("debug: "); pw.print(sDebug); pw.print(" verbose: "); pw.println(sVerbose); } + @GuardedBy("mLock") private String getStateAsStringLocked() { switch (mState) { case STATE_UNKNOWN: @@ -1961,14 +1979,17 @@ public final class AutofillManager { } } + @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 @@ public final class AutofillManager { AutofillValue.forText(node.getText())); } + @GuardedBy("mLock") private void updateTrackedViewsLocked() { if (mTrackedViews != null) { mTrackedViews.onVisibleForAutofillChangedLocked(); @@ -2308,6 +2330,7 @@ public final class AutofillManager { * @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 @@ public final class AutofillManager { * * @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/widget/Editor.java b/core/java/android/widget/Editor.java index 5ab579df5ed6..41ceb306d744 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1299,6 +1299,16 @@ public class Editor { if (mSelectionModifierCursorController != null) { mSelectionModifierCursorController.resetTouchOffsets(); } + + ensureNoSelectionIfNonSelectable(); + } + } + + private void ensureNoSelectionIfNonSelectable() { + // This could be the case if a TextLink has been tapped. + if (!mTextView.textCanBeSelected() && mTextView.hasSelection()) { + Selection.setSelection((Spannable) mTextView.getText(), + mTextView.length(), mTextView.length()); } } @@ -1382,6 +1392,8 @@ public class Editor { // Don't leave us in the middle of a batch edit. Same as in onFocusChanged ensureEndedBatchEdit(); + + ensureNoSelectionIfNonSelectable(); } } diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 012b918ff34e..3aae8497ba1b 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -904,6 +904,9 @@ public class GridLayout extends ViewGroup { } } + /** + * @hide + */ @Override protected void onDebugDrawMargins(Canvas canvas, Paint paint) { // Apply defaults, so as to remove UNDEFINED values @@ -919,6 +922,9 @@ public class GridLayout extends ViewGroup { } } + /** + * @hide + */ @Override protected void onDebugDraw(Canvas canvas) { Paint paint = new Paint(); diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 7a4c800ba15e..88365617cd6e 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -18,8 +18,10 @@ package android.widget; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.annotation.TestApi; import android.annotation.UiThread; import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; @@ -269,4 +271,46 @@ public final class Magnifier { return mWindow.getContentView().findViewById( com.android.internal.R.id.magnifier_image); } + + /** + * @return the content being currently displayed in the magnifier, as bitmap + * + * @hide + */ + @TestApi + public Bitmap getContent() { + return mBitmap; + } + + /** + * @return the position of the magnifier window relative to the screen + * + * @hide + */ + @TestApi + public Rect getWindowPositionOnScreen() { + final int[] viewLocationOnScreen = new int[2]; + mView.getLocationOnScreen(viewLocationOnScreen); + final int[] viewLocationInSurface = new int[2]; + mView.getLocationInSurface(viewLocationInSurface); + + final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0]; + final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1]; + return new Rect(left, top, left + mWindow.getWidth(), top + mWindow.getHeight()); + } + + /** + * @return the size of the magnifier window in dp + * + * @hide + */ + @TestApi + public static PointF getMagnifierDefaultSize() { + final Resources resources = Resources.getSystem(); + final float density = resources.getDisplayMetrics().density; + final PointF size = new PointF(); + size.x = resources.getDimension(com.android.internal.R.dimen.magnifier_width) / density; + size.y = resources.getDimension(com.android.internal.R.dimen.magnifier_height) / density; + return size; + } } diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS index 8f0d02f5d788..2789bae15923 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/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index b22ce5e2a6ee..f6a69d9aeb93 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,157 +16,214 @@ package com.android.internal.app; -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.annotation.Nullable; +import android.animation.TimeAnimator; import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ContentResolver; -import android.content.Intent; -import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.RippleDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; import android.os.Bundle; -import android.provider.Settings; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.MathUtils; -import android.view.Gravity; -import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.animation.PathInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; public class PlatLogoActivity extends Activity { - public static final boolean FINISH = true; + FrameLayout layout; + TimeAnimator anim; + PBackground bg; - FrameLayout mLayout; - int mTapCount; - int mKeyCount; - PathInterpolator mInterpolator = new PathInterpolator(0f, 0f, 0.5f, 1f); + private class PBackground extends Drawable { + private float maxRadius, radius, x, y, dp; + private int[] palette; + private int darkest; + private float offset; + + public PBackground() { + randomizePalette(); + } + + /** + * set inner radius of "p" logo + */ + public void setRadius(float r) { + this.radius = Math.max(48*dp, r); + } + + /** + * move the "p" + */ + public void setPosition(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * for animating the "p" + */ + public void setOffset(float o) { + this.offset = o; + } + + /** + * rough luminance calculation + * https://www.w3.org/TR/AERT/#color-contrast + */ + public float lum(int rgb) { + return ((Color.red(rgb) * 299f) + (Color.green(rgb) * 587f) + (Color.blue(rgb) * 114f)) / 1000f; + } + + /** + * create a random evenly-spaced color palette + * guaranteed to contrast! + */ + public void randomizePalette() { + final int slots = 2 + (int)(Math.random() * 2); + float[] color = new float[] { (float) Math.random() * 360f, 1f, 1f }; + palette = new int[slots]; + darkest = 0; + for (int i=0; i<slots; i++) { + palette[i] = Color.HSVToColor(color); + color[0] += 360f/slots; + if (lum(palette[i]) < lum(palette[darkest])) darkest = i; + } + + final StringBuilder str = new StringBuilder(); + for (int c : palette) { + str.append(String.format("#%08x ", c)); + } + Log.v("PlatLogoActivity", "color palette: " + str); + } + + @Override + public void draw(Canvas canvas) { + if (dp == 0) dp = getResources().getDisplayMetrics().density; + final float width = canvas.getWidth(); + final float height = canvas.getHeight(); + if (radius == 0) { + setPosition(width / 2, height / 2); + setRadius(width / 6); + } + final float inner_w = radius * 0.667f; + + final Paint paint = new Paint(); + paint.setStrokeCap(Paint.Cap.BUTT); + canvas.translate(x, y); + + Path p = new Path(); + p.moveTo(-radius, height); + p.lineTo(-radius, 0); + p.arcTo(-radius, -radius, radius, radius, -180, 270, false); + p.lineTo(-radius, radius); + + float w = Math.max(canvas.getWidth(), canvas.getHeight()) * 1.414f; + paint.setStyle(Paint.Style.FILL); + + int i=0; + while (w > radius*2 + inner_w*2) { + paint.setColor(0xFF000000 | palette[i % palette.length]); + // for a slower but more complete version: + // paint.setStrokeWidth(w); + // canvas.drawPath(p, paint); + canvas.drawOval(-w/2, -w/2, w/2, w/2, paint); + w -= inner_w * (1.1f + Math.sin((i/20f + offset) * 3.14159f)); + i++; + } + + // the innermost circle needs to be a constant color to avoid rapid flashing + paint.setColor(0xFF000000 | palette[(darkest+1) % palette.length]); + canvas.drawOval(-radius, -radius, radius, radius, paint); + + p.reset(); + p.moveTo(-radius, height); + p.lineTo(-radius, 0); + p.arcTo(-radius, -radius, radius, radius, -180, 270, false); + p.lineTo(-radius + inner_w, radius); + + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(inner_w*2); + paint.setColor(palette[darkest]); + canvas.drawPath(p, paint); + paint.setStrokeWidth(inner_w); + paint.setColor(0xFFFFFFFF); + canvas.drawPath(p, paint); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return 0; + } + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mLayout = new FrameLayout(this); - setContentView(mLayout); - } + layout = new FrameLayout(this); + setContentView(layout); - @Override - public void onAttachedToWindow() { - final DisplayMetrics dm = getResources().getDisplayMetrics(); - final float dp = dm.density; - final int size = (int) - (Math.min(Math.min(dm.widthPixels, dm.heightPixels), 600*dp) - 100*dp); - - final ImageView im = new ImageView(this); - final int pad = (int)(40*dp); - im.setPadding(pad, pad, pad, pad); - im.setTranslationZ(20); - im.setScaleX(0.5f); - im.setScaleY(0.5f); - im.setAlpha(0f); - - im.setBackground(new RippleDrawable( - ColorStateList.valueOf(0xFF776677), - getDrawable(com.android.internal.R.drawable.platlogo), - null)); - im.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - final int w = view.getWidth(); - final int h = view.getHeight(); - outline.setOval((int)(w*.125), (int)(h*.125), (int)(w*.96), (int)(h*.96)); - } - }); - im.setElevation(12f*dp); - im.setClickable(true); - im.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - im.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mTapCount < 5) return false; - - final ContentResolver cr = getContentResolver(); - if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) - == 0) { - // For posterity: the moment this user unlocked the easter egg - try { - Settings.System.putLong(cr, - Settings.System.EGG_MODE, - System.currentTimeMillis()); - } catch (RuntimeException e) { - Log.e("PlatLogoActivity", "Can't write settings", e); - } - } - im.post(new Runnable() { - @Override - public void run() { - try { - startActivity(new Intent(Intent.ACTION_MAIN) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .addCategory("com.android.internal.category.PLATLOGO")); - } catch (ActivityNotFoundException ex) { - Log.e("PlatLogoActivity", "No more eggs."); - } - if (FINISH) finish(); - } - }); - return true; - } - }); - mTapCount++; - } - }); + bg = new PBackground(); + layout.setBackground(bg); + + layout.setOnTouchListener(new View.OnTouchListener() { + final PointerCoords pc0 = new PointerCoords(); + final PointerCoords pc1 = new PointerCoords(); - // Enable hardware keyboard input for TV compatibility. - im.setFocusable(true); - im.requestFocus(); - im.setOnKeyListener(new View.OnKeyListener() { @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode != KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { - ++mKeyCount; - if (mKeyCount > 2) { - if (mTapCount > 5) { - im.performLongClick(); - } else { - im.performClick(); + public boolean onTouch(View v, MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + if (event.getPointerCount() > 1) { + event.getPointerCoords(0, pc0); + event.getPointerCoords(1, pc1); + bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f); } - } - return true; - } else { - return false; + break; } + return true; } }); + } + + @Override + public void onStart() { + super.onStart(); + + bg.randomizePalette(); - mLayout.addView(im, new FrameLayout.LayoutParams(size, size, Gravity.CENTER)); + anim = new TimeAnimator(); + anim.setTimeListener( + new TimeAnimator.TimeListener() { + @Override + public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { + bg.setOffset((float) totalTime / 60000f); + bg.invalidateSelf(); + } + }); - im.animate().scaleX(1f).scaleY(1f).alpha(1f) - .setInterpolator(mInterpolator) - .setDuration(500) - .setStartDelay(800) - .start(); + anim.start(); + } + + @Override + public void onStop() { + if (anim != null) { + anim.cancel(); + anim = null; + } + super.onStop(); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index cf3e073a8cbb..eb58b0919385 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -678,7 +678,8 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { @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 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; } - void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { + void addHistoryBufferLocked(long elapsedRealtimeMs, HistoryItem cur) { if (!mHaveBatteryLevel || !mRecordingHistory) { return; } @@ -3603,8 +3606,8 @@ public class BatteryStatsImpl extends BatteryStats { } else if (dataSize >= MAX_HISTORY_BUFFER) { if (!mHistoryOverflow) { mHistoryOverflow = true; - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur); return; } @@ -3642,7 +3645,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); return; } @@ -3650,15 +3653,14 @@ public class BatteryStatsImpl extends BatteryStats { // The history is currently empty; we need it to start with a time stamp. cur.currentTime = System.currentTimeMillis(); if (recordResetDueToOverflow) { - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur); } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur); } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); } - private void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, - HistoryItem cur) { + private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) { if (mIteratingHistory) { throw new IllegalStateException("Can't do this while iterating history!"); } @@ -3692,17 +3694,17 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryAddTmp.wakeReasonTag = null; mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE; mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG; - addHistoryRecordInnerLocked(wakeElapsedTime, uptimeMs, mHistoryAddTmp); + addHistoryRecordInnerLocked(wakeElapsedTime, mHistoryAddTmp); } } mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG; mTrackRunningHistoryElapsedRealtime = elapsedRealtimeMs; mTrackRunningHistoryUptime = uptimeMs; - addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur); + addHistoryRecordInnerLocked(elapsedRealtimeMs, mHistoryCur); } - void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur); + void addHistoryRecordInnerLocked(long elapsedRealtimeMs, HistoryItem cur) { + addHistoryBufferLocked(elapsedRealtimeMs, cur); if (!USE_OLD_HISTORY) { return; @@ -3743,7 +3745,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mNumHistoryItems == MAX_HISTORY_ITEMS || mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) { - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW, cur); } if (mNumHistoryItems >= MAX_HISTORY_ITEMS) { @@ -3760,7 +3762,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur); } public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, @@ -3826,6 +3828,7 @@ public class BatteryStatsImpl extends BatteryStats { mActiveHistoryStates2 = 0xffffffff; } + @GuardedBy("this") public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime, long realtime) { final boolean screenOff = !isScreenOn(screenState); @@ -3903,6 +3906,7 @@ public class BatteryStatsImpl extends BatteryStats { * 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 @@ public class BatteryStatsImpl extends BatteryStats { return; } + @GuardedBy("this") public void noteScreenStateLocked(int state) { state = mPretendScreenOff ? Display.STATE_OFF : state; @@ -9648,6 +9653,7 @@ public class BatteryStatsImpl extends BatteryStats { return ps; } + @GuardedBy("mBsi") public void updateUidProcessStateLocked(int procState) { int uidRunningState; // Make special note of Foreground Services @@ -9931,8 +9937,6 @@ public class BatteryStatsImpl extends BatteryStats { if (wl != null) { StopwatchTimer wlt = getWakelockTimerLocked(wl, type); wlt.stopRunningLocked(elapsedRealtimeMs); - if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped - } } if (type == WAKE_TYPE_PARTIAL) { if (mAggregatedPartialWakelockTimer != null) { @@ -11214,6 +11218,9 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { * 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { boolean reset) { mRecordingHistory = true; mHistoryCur.currentTime = System.currentTimeMillis(); - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, + addHistoryBufferLocked(elapsedRealtimeMs, reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME, mHistoryCur); mHistoryCur.currentTime = 0; @@ -12320,8 +12367,7 @@ public class BatteryStatsImpl extends BatteryStats { final long uptimeMs) { if (mRecordingHistory) { mHistoryCur.currentTime = currentTime; - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME, - mHistoryCur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur); mHistoryCur.currentTime = 0; } } @@ -12329,8 +12375,7 @@ public class BatteryStatsImpl extends BatteryStats { private void recordShutdownLocked(final long elapsedRealtimeMs, final long uptimeMs) { if (mRecordingHistory) { mHistoryCur.currentTime = System.currentTimeMillis(); - addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_SHUTDOWN, - mHistoryCur); + addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur); mHistoryCur.currentTime = 0; } } @@ -12344,6 +12389,7 @@ public class BatteryStatsImpl extends BatteryStats { // 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 @@ public class BatteryStatsImpl extends BatteryStats { } } + @GuardedBy("this") public void dumpConstantsLocked(PrintWriter pw) { mConstants.dumpLocked(pw); } @@ -13274,7 +13321,7 @@ public class BatteryStatsImpl extends BatteryStats { if (USE_OLD_HISTORY) { addHistoryRecordLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur); } - addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur); + addHistoryBufferLocked(elapsedRealtime, HistoryItem.CMD_START, mHistoryCur); startRecordingHistory(elapsedRealtime, uptime, false); } @@ -13542,6 +13589,7 @@ public class BatteryStatsImpl extends BatteryStats { mCameraOnTimer.readSummaryFromParcelLocked(in); mBluetoothScanNesting = 0; mBluetoothScanTimer.readSummaryFromParcelLocked(in); + mIsCellularTxPowerHigh = false; int NRPMS = in.readInt(); if (NRPMS > 10000) { @@ -14478,6 +14526,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 088e7268b984..12405ebce057 100644 --- a/core/java/com/android/internal/os/FuseAppLoop.java +++ b/core/java/com/android/internal/os/FuseAppLoop.java @@ -283,6 +283,7 @@ public class FuseAppLoop implements Handler.Callback { 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 @@ public class FuseAppLoop implements Handler.Callback { 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 f70d3c2a27fd..221bf8823b74 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -35,7 +35,7 @@ oneway interface IStatusBar 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/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index b53459e072d4..67dc81af5895 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 @@ public class MenuBuilder implements Menu { 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 000000000000..71d3bb5d6b5c --- /dev/null +++ b/core/java/com/android/internal/widget/LocalImageResolver.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.widget; + +import android.annotation.Nullable; +import android.app.Notification; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A class to extract Bitmaps from a MessagingStyle message. + */ +public class LocalImageResolver { + + private static final int MAX_SAFE_ICON_SIZE_PX = 480; + + @Nullable + public static Drawable resolveImage(Uri uri, Context context) throws IOException { + BitmapFactory.Options onlyBoundsOptions = getBoundsOptionsForImage(uri, context); + if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) { + return null; + } + + int originalSize = + (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) + ? onlyBoundsOptions.outHeight + : onlyBoundsOptions.outWidth; + + double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX) + ? (originalSize / MAX_SAFE_ICON_SIZE_PX) + : 1.0; + + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio); + InputStream input = context.getContentResolver().openInputStream(uri); + Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); + input.close(); + return new BitmapDrawable(context.getResources(), bitmap); + } + + private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context) + throws IOException { + InputStream input = context.getContentResolver().openInputStream(uri); + BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); + onlyBoundsOptions.inJustDecodeBounds = true; + BitmapFactory.decodeStream(input, null, onlyBoundsOptions); + input.close(); + return onlyBoundsOptions; + } + + private static int getPowerOfTwoForSampleRatio(double ratio) { + int k = Integer.highestOneBit((int) Math.floor(ratio)); + return Math.max(1, k); + } +} diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 5577d6e86fee..239beaa220e0 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -22,9 +22,12 @@ import android.annotation.Nullable; import android.annotation.StyleRes; import android.app.Notification; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.Icon; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Pools; import android.view.LayoutInflater; import android.view.View; @@ -61,6 +64,11 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private boolean mIsHidingAnimated; private boolean mNeedsGeneratedAvatar; private Notification.Person mSender; + private boolean mAvatarsAtEnd; + private ViewGroup mImageContainer; + private MessagingImageMessage mIsolatedMessage; + private boolean mTransformingImages; + private Point mDisplaySize = new Point(); public MessagingGroup(@NonNull Context context) { super(context); @@ -87,6 +95,35 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou mSenderName = findViewById(R.id.message_name); mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); mAvatarView = findViewById(R.id.message_icon); + mImageContainer = findViewById(R.id.messaging_group_icon_container); + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + mDisplaySize.x = displayMetrics.widthPixels; + mDisplaySize.y = displayMetrics.heightPixels; + } + + public void updateClipRect() { + // We want to clip to the senderName if it's available, otherwise our images will come + // from a weird position + Rect clipRect; + if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) { + ViewGroup parent = (ViewGroup) mSenderName.getParent(); + int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent( + mMessageContainer, parent) + mSenderName.getHeight(); + clipRect = new Rect(0, top, mDisplaySize.x, mDisplaySize.y); + } else { + clipRect = null; + } + mMessageContainer.setClipBounds(clipRect); + } + + private int getDistanceFromParent(View searchedView, ViewGroup parent) { + int position = 0; + View view = searchedView; + while(view != parent) { + position += view.getTop() + view.getTranslationY(); + view = (View) view.getParent(); + } + return position; } public void setSender(Notification.Person sender, CharSequence nameOverride) { @@ -129,12 +166,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } public void removeMessage(MessagingMessage messagingMessage) { - mMessageContainer.removeView(messagingMessage); + ViewGroup messageParent = (ViewGroup) messagingMessage.getView().getParent(); + messageParent.removeView(messagingMessage.getView()); Runnable recycleRunnable = () -> { - mMessageContainer.removeTransientView(messagingMessage); + messageParent.removeTransientView(messagingMessage.getView()); messagingMessage.recycle(); if (mMessageContainer.getChildCount() == 0 - && mMessageContainer.getTransientViewCount() == 0) { + && mMessageContainer.getTransientViewCount() == 0 + && mImageContainer.getChildCount() == 0) { ViewParent parent = getParent(); if (parent instanceof ViewGroup) { ((ViewGroup) parent).removeView(MessagingGroup.this); @@ -148,9 +187,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } }; if (isShown()) { - mMessageContainer.addTransientView(messagingMessage, 0); - performRemoveAnimation(messagingMessage, recycleRunnable); - if (mMessageContainer.getChildCount() == 0) { + messageParent.addTransientView(messagingMessage.getView(), 0); + performRemoveAnimation(messagingMessage.getView(), recycleRunnable); + if (mMessageContainer.getChildCount() == 0 + && mImageContainer.getChildCount() == 0) { removeGroupAnimated(null); } } else { @@ -160,12 +200,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } private void removeGroupAnimated(Runnable endAction) { - MessagingPropertyAnimator.fadeOut(mAvatarView, null); - MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView, - (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN); - MessagingPropertyAnimator.fadeOut(mSenderName, null); - MessagingPropertyAnimator.startLocalTranslationTo(mSenderName, - (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN); + performRemoveAnimation(mAvatarView, null); + performRemoveAnimation(mSenderName, null); boolean endActionTriggered = false; for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) { View child = mMessageContainer.getChildAt(i); @@ -182,14 +218,17 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou performRemoveAnimation(child, childEndAction); endActionTriggered = true; } + if (mIsolatedMessage != null) { + performRemoveAnimation(mIsolatedMessage, !endActionTriggered ? endAction : null); + endActionTriggered = true; + } if (!endActionTriggered && endAction != null) { endAction.run(); } } - public void performRemoveAnimation(View message, - Runnable recycleRunnable) { - MessagingPropertyAnimator.fadeOut(message, recycleRunnable); + public void performRemoveAnimation(View message, Runnable endAction) { + MessagingPropertyAnimator.fadeOut(message, endAction); MessagingPropertyAnimator.startLocalTranslationTo(message, (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN); } @@ -222,6 +261,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou } } } + if (mMessageContainer.getChildCount() == 0 && mIsolatedMessage != null) { + return mIsolatedMessage.getMeasuredType(); + } return MEASURED_NORMAL; } @@ -234,6 +276,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines(); } } + result = mIsolatedMessage != null ? Math.max(result, 1) : result; // A group is usually taking up quite some space with the padding and the name, let's add 1 return result + 1; } @@ -289,26 +332,67 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setMessages(List<MessagingMessage> group) { // Let's now make sure all children are added and in the correct order + int textMessageIndex = 0; + MessagingImageMessage isolatedMessage = null; for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) { MessagingMessage message = group.get(messageIndex); + message.setColor(mTextColor); if (message.getGroup() != this) { message.setMessagingGroup(this); - ViewParent parent = mMessageContainer.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(message); - } - mMessageContainer.addView(message, messageIndex); mAddedMessages.add(message); } - if (messageIndex != mMessageContainer.indexOfChild(message)) { - mMessageContainer.removeView(message); - mMessageContainer.addView(message, messageIndex); + boolean isImage = message instanceof MessagingImageMessage; + if (mAvatarsAtEnd && isImage) { + isolatedMessage = (MessagingImageMessage) message; + } else { + if (removeFromParentIfDifferent(message, mMessageContainer)) { + ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams(); + if (layoutParams != null + && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) { + message.getView().setLayoutParams( + mMessageContainer.generateDefaultLayoutParams()); + } + mMessageContainer.addView(message.getView(), textMessageIndex); + } + if (isImage) { + ((MessagingImageMessage) message).setIsolated(false); + } + // Let's sort them properly + if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) { + mMessageContainer.removeView(message.getView()); + mMessageContainer.addView(message.getView(), textMessageIndex); + } + textMessageIndex++; + } + } + if (isolatedMessage != null) { + if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) { + mImageContainer.removeAllViews(); + mImageContainer.addView(isolatedMessage.getView()); } - message.setTextColor(mTextColor); + isolatedMessage.setIsolated(true); + } else if (mIsolatedMessage != null) { + mImageContainer.removeAllViews(); } + mIsolatedMessage = isolatedMessage; mMessages = group; } + /** + * Remove the message from the parent if the parent isn't the one provided + * @return whether the message was removed + */ + private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) { + ViewParent parent = message.getView().getParent(); + if (parent != newParent) { + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(message.getView()); + } + return true; + } + return false; + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -317,13 +401,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou @Override public boolean onPreDraw() { for (MessagingMessage message : mAddedMessages) { - if (!message.isShown()) { + if (!message.getView().isShown()) { continue; } - MessagingPropertyAnimator.fadeIn(message); + MessagingPropertyAnimator.fadeIn(message.getView()); if (!mFirstLayout) { - MessagingPropertyAnimator.startLocalTranslationFrom(message, - message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN); + MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(), + message.getView().getHeight(), + MessagingLayout.LINEAR_OUT_SLOW_IN); } } mAddedMessages.clear(); @@ -333,6 +418,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou }); } mFirstLayout = false; + updateClipRect(); } /** @@ -372,6 +458,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return mMessageContainer; } + public MessagingImageMessage getIsolatedMessage() { + return mIsolatedMessage; + } + public boolean needsGeneratedAvatar() { return mNeedsGeneratedAvatar; } @@ -379,4 +469,19 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public Notification.Person getSender() { return mSender; } + + public void setTransformingImages(boolean transformingImages) { + mTransformingImages = transformingImages; + } + + public void setDisplayAvatarsAtEnd(boolean atEnd) { + if (mAvatarsAtEnd != atEnd) { + mAvatarsAtEnd = atEnd; + mImageContainer.setVisibility(atEnd ? View.VISIBLE : View.GONE); + } + } + + public List<MessagingMessage> getMessages() { + return mMessages; + } } diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java new file mode 100644 index 000000000000..961f90a3c110 --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingImageMessage.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.widget; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StyleRes; +import android.app.Notification; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Pools; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RemoteViews; + +import com.android.internal.R; + +import java.io.IOException; + +/** + * A message of a {@link MessagingLayout} that is an image. + */ +@RemoteViews.RemoteView +public class MessagingImageMessage extends ImageView implements MessagingMessage { + private static final String TAG = "MessagingImageMessage"; + private static Pools.SimplePool<MessagingImageMessage> sInstancePool + = new Pools.SynchronizedPool<>(10); + private final MessagingMessageState mState = new MessagingMessageState(this); + private final int mMinImageHeight; + private final Path mPath = new Path(); + private final int mImageRounding; + private final int mMaxImageHeight; + private final int mIsolatedSize; + private final int mExtraSpacing; + private Drawable mDrawable; + private float mAspectRatio; + private int mActualWidth; + private int mActualHeight; + private boolean mIsIsolated; + + public MessagingImageMessage(@NonNull Context context) { + this(context, null); + } + + public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mMinImageHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_min_size); + mMaxImageHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_max_height); + mImageRounding = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_rounding); + mExtraSpacing = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.messaging_image_extra_spacing); + setMaxHeight(mMaxImageHeight); + mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size); + } + + @Override + public MessagingMessageState getState() { + return mState; + } + + @Override + public boolean setMessage(Notification.MessagingStyle.Message message) { + MessagingMessage.super.setMessage(message); + Drawable drawable; + try { + drawable = LocalImageResolver.resolveImage(message.getDataUri(), getContext()); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + int intrinsicHeight = drawable.getIntrinsicHeight(); + if (intrinsicHeight == 0) { + Log.w(TAG, "Drawable with 0 intrinsic height was returned"); + return false; + } + mDrawable = drawable; + mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight; + setImageDrawable(drawable); + setContentDescription(message.getText()); + return true; + } + + static MessagingMessage createMessage(MessagingLayout layout, + Notification.MessagingStyle.Message m) { + MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); + MessagingImageMessage createdMessage = sInstancePool.acquire(); + if (createdMessage == null) { + createdMessage = (MessagingImageMessage) LayoutInflater.from( + layout.getContext()).inflate( + R.layout.notification_template_messaging_image_message, + messagingLinearLayout, + false); + createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); + } + boolean created = createdMessage.setMessage(m); + if (!created) { + createdMessage.recycle(); + return MessagingTextMessage.createMessage(layout, m); + } + return createdMessage; + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.save(); + canvas.clipPath(getRoundedRectPath()); + int width = (int) Math.max(getActualWidth(), getActualHeight() * mAspectRatio); + int height = (int) (width / mAspectRatio); + int left = (int) ((getActualWidth() - width) / 2.0f); + mDrawable.setBounds(left, 0, left + width, height); + mDrawable.draw(canvas); + canvas.restore(); + } + + public Path getRoundedRectPath() { + int left = 0; + int right = getActualWidth(); + int top = 0; + int bottom = getActualHeight(); + mPath.reset(); + int width = right - left; + float roundnessX = mImageRounding; + float roundnessY = mImageRounding; + roundnessX = Math.min(width / 2, roundnessX); + roundnessY = Math.min((bottom - top) / 2, roundnessY); + mPath.moveTo(left, top + roundnessY); + mPath.quadTo(left, top, left + roundnessX, top); + mPath.lineTo(right - roundnessX, top); + mPath.quadTo(right, top, right, top + roundnessY); + mPath.lineTo(right, bottom - roundnessY); + mPath.quadTo(right, bottom, right - roundnessX, bottom); + mPath.lineTo(left + roundnessX, bottom); + mPath.quadTo(left, bottom, left, bottom - roundnessY); + mPath.close(); + return mPath; + } + + public void recycle() { + MessagingMessage.super.recycle(); + setAlpha(1.0f); + setTranslationY(0); + setImageBitmap(null); + mDrawable = null; + sInstancePool.release(this); + } + + public static void dropCache() { + sInstancePool = new Pools.SynchronizedPool<>(10); + } + + @Override + public int getMeasuredType() { + int measuredHeight = getMeasuredHeight(); + int minImageHeight; + if (mIsIsolated) { + minImageHeight = mIsolatedSize; + } else { + minImageHeight = mMinImageHeight; + } + boolean measuredTooSmall = measuredHeight < minImageHeight + && measuredHeight != mDrawable.getIntrinsicHeight(); + if (measuredTooSmall) { + return MEASURED_TOO_SMALL; + } else { + if (!mIsIsolated && measuredHeight != mDrawable.getIntrinsicHeight()) { + return MEASURED_SHORTENED; + } else { + return MEASURED_NORMAL; + } + } + } + + @Override + public void setMaxDisplayedLines(int lines) { + // Nothing to do, this should be handled automatically. + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mIsIsolated) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + // TODO: ensure that this isn't called when transforming + setActualWidth(getStaticWidth()); + setActualHeight(getHeight()); + } + + @Override + public int getConsumedLines() { + return 3; + } + + public void setActualWidth(int actualWidth) { + mActualWidth = actualWidth; + invalidate(); + } + + public int getActualWidth() { + return mActualWidth; + } + + public void setActualHeight(int actualHeight) { + mActualHeight = actualHeight; + invalidate(); + } + + public int getActualHeight() { + return mActualHeight; + } + + public int getStaticWidth() { + if (mIsIsolated) { + return getWidth(); + } + return (int) (getHeight() * mAspectRatio); + } + + public void setIsolated(boolean isolated) { + if (mIsIsolated != isolated) { + mIsIsolated = isolated; + // update the layout params not to have margins + ViewGroup.MarginLayoutParams layoutParams = + (ViewGroup.MarginLayoutParams) getLayoutParams(); + layoutParams.topMargin = isolated ? 0 : mExtraSpacing; + setLayoutParams(layoutParams); + } + } + + @Override + public int getExtraSpacing() { + return mExtraSpacing; + } +} diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index d45c086e3b52..5279636ae2a0 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -81,6 +81,7 @@ public class MessagingLayout extends FrameLayout { private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>(); private Notification.Person mUser; private CharSequence mNameReplacement; + private boolean mIsCollapsed; public MessagingLayout(@NonNull Context context) { super(context); @@ -127,6 +128,11 @@ public class MessagingLayout extends FrameLayout { } @RemotableViewMethod + public void setIsCollapsed(boolean isCollapsed) { + mIsCollapsed = isCollapsed; + } + + @RemotableViewMethod public void setData(Bundle extras) { Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); List<Notification.MessagingStyle.Message> newMessages @@ -331,6 +337,7 @@ public class MessagingLayout extends FrameLayout { newGroup = MessagingGroup.createGroup(mMessagingLinearLayout); mAddedGroups.add(newGroup); } + newGroup.setDisplayAvatarsAtEnd(mIsCollapsed); newGroup.setLayoutColor(mLayoutColor); Notification.Person sender = senders.get(groupIndex); CharSequence nameOverride = null; @@ -392,7 +399,6 @@ public class MessagingLayout extends FrameLayout { MessagingMessage message = findAndRemoveMatchingMessage(m); if (message == null) { message = MessagingMessage.createMessage(this, m); - message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR); } message.setIsHistoric(historic); result.add(message); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index f0ef37076618..991e3e7a80f9 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -75,7 +75,6 @@ public class MessagingLinearLayout extends ViewGroup { targetHeight = Integer.MAX_VALUE; break; } - int widthSize = MeasureSpec.getSize(widthMeasureSpec); // Now that we know which views to take, fix up the indents and see what width we get. int measuredWidth = mPaddingLeft + mPaddingRight; @@ -90,7 +89,6 @@ public class MessagingLinearLayout extends ViewGroup { totalHeight = mPaddingTop + mPaddingBottom; boolean first = true; int linesRemaining = mMaxDisplayedLines; - // Starting from the bottom: we measure every view as if it were the only one. If it still // fits, we take it, otherwise we stop there. for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { @@ -100,11 +98,13 @@ public class MessagingLinearLayout extends ViewGroup { final View child = getChildAt(i); LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); MessagingChild messagingChild = null; + int spacing = mSpacing; if (child instanceof MessagingChild) { messagingChild = (MessagingChild) child; messagingChild.setMaxDisplayedLines(linesRemaining); + spacing += messagingChild.getExtraSpacing(); } - int spacing = first ? 0 : mSpacing; + spacing = first ? 0 : spacing; measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight - mPaddingTop - mPaddingBottom + spacing); @@ -254,6 +254,9 @@ public class MessagingLinearLayout extends ViewGroup { void setMaxDisplayedLines(int lines); void hideAnimated(); boolean isHidingAnimated(); + default int getExtraSpacing() { + return 0; + } } public static class LayoutParams extends MarginLayoutParams { diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index f09621f544bc..bf1c5ca747a4 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -16,182 +16,125 @@ package com.android.internal.widget; -import android.annotation.AttrRes; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StyleRes; import android.app.Notification; -import android.content.Context; -import android.text.Layout; -import android.util.AttributeSet; -import android.util.Pools; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.RemoteViews; - -import com.android.internal.R; +import android.view.View; import java.util.Objects; /** * A message of a {@link MessagingLayout}. */ -@RemoteViews.RemoteView -public class MessagingMessage extends ImageFloatingTextView implements - MessagingLinearLayout.MessagingChild { - - private static Pools.SimplePool<MessagingMessage> sInstancePool - = new Pools.SynchronizedPool<>(10); - private Notification.MessagingStyle.Message mMessage; - private MessagingGroup mGroup; - private boolean mIsHistoric; - private boolean mIsHidingAnimated; +public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { - public MessagingMessage(@NonNull Context context) { - super(context); - } + /** + * Prefix for supported image MIME types + **/ + String IMAGE_MIME_TYPE_PREFIX = "image/"; - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); + static MessagingMessage createMessage(MessagingLayout layout, + Notification.MessagingStyle.Message m) { + if (hasImage(m)) { + return MessagingImageMessage.createMessage(layout, m); + } else { + return MessagingTextMessage.createMessage(layout, m); + } } - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { - super(context, attrs, defStyleAttr); + static void dropCache() { + MessagingTextMessage.dropCache(); + MessagingImageMessage.dropCache(); } - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + static boolean hasImage(Notification.MessagingStyle.Message m) { + return m.getDataUri() != null + && m.getDataMimeType() != null + && m.getDataMimeType().startsWith(IMAGE_MIME_TYPE_PREFIX); } - private void setMessage(Notification.MessagingStyle.Message message) { - mMessage = message; - setText(message.getText()); + /** + * Set a message for this view. + * @return true if setting the message worked + */ + default boolean setMessage(Notification.MessagingStyle.Message message) { + getState().setMessage(message); + return true; } - public Notification.MessagingStyle.Message getMessage() { - return mMessage; + default Notification.MessagingStyle.Message getMessage() { + return getState().getMessage(); } - boolean sameAs(Notification.MessagingStyle.Message message) { - if (!Objects.equals(message.getText(), mMessage.getText())) { + default boolean sameAs(Notification.MessagingStyle.Message message) { + Notification.MessagingStyle.Message ownMessage = getMessage(); + if (!Objects.equals(message.getText(), ownMessage.getText())) { return false; } - if (!Objects.equals(message.getSender(), mMessage.getSender())) { + if (!Objects.equals(message.getSender(), ownMessage.getSender())) { return false; } - if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) { + if (!Objects.equals(message.getTimestamp(), ownMessage.getTimestamp())) { + return false; + } + if (!Objects.equals(message.getDataMimeType(), ownMessage.getDataMimeType())) { + return false; + } + if (!Objects.equals(message.getDataUri(), ownMessage.getDataUri())) { return false; } return true; } - boolean sameAs(MessagingMessage message) { + default boolean sameAs(MessagingMessage message) { return sameAs(message.getMessage()); } - static MessagingMessage createMessage(MessagingLayout layout, - Notification.MessagingStyle.Message m) { - MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); - MessagingMessage createdMessage = sInstancePool.acquire(); - if (createdMessage == null) { - createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate( - R.layout.notification_template_messaging_message, messagingLinearLayout, - false); - } - createdMessage.setMessage(m); - return createdMessage; - } - - public void removeMessage() { - mGroup.removeMessage(this); - } - - public void recycle() { - mGroup = null; - mMessage = null; - setAlpha(1.0f); - setTranslationY(0); - sInstancePool.release(this); + default void removeMessage() { + getGroup().removeMessage(this); } - public void setMessagingGroup(MessagingGroup group) { - mGroup = group; + default void setMessagingGroup(MessagingGroup group) { + getState().setGroup(group); } - public static void dropCache() { - sInstancePool = new Pools.SynchronizedPool<>(10); + default void setIsHistoric(boolean isHistoric) { + getState().setIsHistoric(isHistoric); } - public void setIsHistoric(boolean isHistoric) { - mIsHistoric = isHistoric; + default MessagingGroup getGroup() { + return getState().getGroup(); } - public MessagingGroup getGroup() { - return mGroup; + default void setIsHidingAnimated(boolean isHiding) { + getState().setIsHidingAnimated(isHiding); } @Override - public int getMeasuredType() { - boolean measuredTooSmall = getMeasuredHeight() - < getLayoutHeight() + getPaddingTop() + getPaddingBottom(); - if (measuredTooSmall) { - return MEASURED_TOO_SMALL; - } else { - Layout layout = getLayout(); - if (layout == null) { - return MEASURED_TOO_SMALL; - } - if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) { - return MEASURED_SHORTENED; - } else { - return MEASURED_NORMAL; - } - } + default boolean isHidingAnimated() { + return getState().isHidingAnimated(); } @Override - public void hideAnimated() { + default void hideAnimated() { setIsHidingAnimated(true); - mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false)); + getGroup().performRemoveAnimation(getState().getHostView(), + () -> setIsHidingAnimated(false)); } - private void setIsHidingAnimated(boolean isHiding) { - ViewParent parent = getParent(); - mIsHidingAnimated = isHiding; - invalidate(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).invalidate(); - } + default boolean hasOverlappingRendering() { + return false; } - @Override - public boolean isHidingAnimated() { - return mIsHidingAnimated; + default void recycle() { + getState().reset(); } - @Override - public void setMaxDisplayedLines(int lines) { - setMaxLines(lines); + default View getView() { + return (View) this; } - @Override - public int getConsumedLines() { - return getLineCount(); - } + default void setColor(int textColor) {} - public int getLayoutHeight() { - Layout layout = getLayout(); - if (layout == null) { - return 0; - } - return layout.getHeight(); - } + MessagingMessageState getState(); - @Override - public boolean hasOverlappingRendering() { - return false; - } + void setVisibility(int visibility); } diff --git a/core/java/com/android/internal/widget/MessagingMessageState.java b/core/java/com/android/internal/widget/MessagingMessageState.java new file mode 100644 index 000000000000..ac624728689d --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingMessageState.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.widget; + +import android.app.Notification; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +/** + * Shared state and implementation for MessagingMessages. Used to share common implementations. + */ +public class MessagingMessageState { + private final View mHostView; + private Notification.MessagingStyle.Message mMessage; + private MessagingGroup mGroup; + private boolean mIsHistoric; + private boolean mIsHidingAnimated; + + MessagingMessageState(View hostView) { + mHostView = hostView; + } + + public void setMessage(Notification.MessagingStyle.Message message) { + mMessage = message; + } + + public Notification.MessagingStyle.Message getMessage() { + return mMessage; + } + + public void setGroup(MessagingGroup group) { + mGroup = group; + } + + public MessagingGroup getGroup() { + return mGroup; + } + + public void setIsHistoric(boolean isHistoric) { + mIsHistoric = isHistoric; + } + + public void setIsHidingAnimated(boolean isHiding) { + ViewParent parent = mHostView.getParent(); + mIsHidingAnimated = isHiding; + mHostView.invalidate(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).invalidate(); + } + } + + public boolean isHidingAnimated() { + return mIsHidingAnimated; + } + + public View getHostView() { + return mHostView; + } + + public void reset() { + mIsHidingAnimated = false; + mIsHistoric = false; + mGroup = null; + mMessage = null; + } +} diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java new file mode 100644 index 000000000000..794cc1dc66f7 --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingTextMessage.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.internal.widget; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StyleRes; +import android.app.Notification; +import android.content.Context; +import android.text.Layout; +import android.util.AttributeSet; +import android.util.Pools; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.RemoteViews; + +import com.android.internal.R; + +import java.util.Objects; + +/** + * A message of a {@link MessagingLayout}. + */ +@RemoteViews.RemoteView +public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage { + + private static Pools.SimplePool<MessagingTextMessage> sInstancePool + = new Pools.SynchronizedPool<>(20); + private final MessagingMessageState mState = new MessagingMessageState(this); + + public MessagingTextMessage(@NonNull Context context) { + super(context); + } + + public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public MessagingMessageState getState() { + return mState; + } + + @Override + public boolean setMessage(Notification.MessagingStyle.Message message) { + MessagingMessage.super.setMessage(message); + setText(message.getText()); + return true; + } + + static MessagingMessage createMessage(MessagingLayout layout, + Notification.MessagingStyle.Message m) { + MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout(); + MessagingTextMessage createdMessage = sInstancePool.acquire(); + if (createdMessage == null) { + createdMessage = (MessagingTextMessage) LayoutInflater.from( + layout.getContext()).inflate( + R.layout.notification_template_messaging_text_message, + messagingLinearLayout, + false); + createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR); + } + createdMessage.setMessage(m); + return createdMessage; + } + + public void recycle() { + MessagingMessage.super.recycle(); + setAlpha(1.0f); + setTranslationY(0); + sInstancePool.release(this); + } + + public static void dropCache() { + sInstancePool = new Pools.SynchronizedPool<>(10); + } + + @Override + public int getMeasuredType() { + boolean measuredTooSmall = getMeasuredHeight() + < getLayoutHeight() + getPaddingTop() + getPaddingBottom(); + if (measuredTooSmall) { + return MEASURED_TOO_SMALL; + } else { + Layout layout = getLayout(); + if (layout == null) { + return MEASURED_TOO_SMALL; + } + if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) { + return MEASURED_SHORTENED; + } else { + return MEASURED_NORMAL; + } + } + } + + @Override + public void setMaxDisplayedLines(int lines) { + setMaxLines(lines); + } + + @Override + public int getConsumedLines() { + return getLineCount(); + } + + public int getLayoutHeight() { + Layout layout = getLayout(); + if (layout == null) { + return 0; + } + return layout.getHeight(); + } + + @Override + public void setColor(int color) { + setTextColor(color); + } +} diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java index ad6020c0846c..7d1c70647092 100644 --- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java +++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java @@ -98,6 +98,8 @@ public final class VerifyCredentialResponse implements Parcelable { 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 fb186693979d..efd98e2ba96b 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -164,7 +164,7 @@ public class BootReceiver extends BroadcastReceiver { .append("Revision: ") .append(SystemProperties.get("ro.revision", "")).append("\n") .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") - .append("Radio: ").append(Build.RADIO).append("\n") + .append("Radio: ").append(Build.getRadioVersion()).append("\n") .append("Kernel: ") .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) .append("\n").toString(); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 33f80ce8ffb0..78a3e137ccb0 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -110,8 +110,8 @@ cc_library_shared { "android_util_AssetManager.cpp", "android_util_Binder.cpp", "android_util_EventLog.cpp", - "android_util_MemoryIntArray.cpp", "android_util_Log.cpp", + "android_util_MemoryIntArray.cpp", "android_util_PathParser.cpp", "android_util_Process.cpp", "android_util_StringBlock.cpp", @@ -191,6 +191,7 @@ cc_library_shared { "android_backup_FileBackupHelperBase.cpp", "android_backup_BackupHelperDispatcher.cpp", "android_app_backup_FullBackup.cpp", + "android_content_res_ApkAssets.cpp", "android_content_res_ObbScanner.cpp", "android_content_res_Configuration.cpp", "android_animation_PropertyValuesHolder.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d20217386b1e..4a032c4bbae4 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -123,6 +123,7 @@ extern int register_android_util_MemoryIntArray(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_content_StringBlock(JNIEnv* env); extern int register_android_content_XmlBlock(JNIEnv* env); +extern int register_android_content_res_ApkAssets(JNIEnv* env); extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); @@ -1346,6 +1347,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), REG_JNI(register_android_content_XmlBlock), + REG_JNI(register_android_content_res_ApkAssets), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), REG_JNI(register_android_text_MeasuredParagraph), diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 937b3ffb9d60..ed032c78f6c7 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 void FontFamily_unref(jlong familyPtr) { } 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 @@ static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, in 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 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b NPE_CHECK_RETURN_ZERO(env, jpath); NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); - AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr); + + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, jassetMgr); if (NULL == mgr) { builder->axes.clear(); return false; @@ -229,27 +218,33 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b return false; } - Asset* asset; - if (isAsset) { - asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - } else { - asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(), - Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + if (isAsset) { + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + } else if (cookie > 0) { + // Valid java cookies are 1-based, but AssetManager cookies are 0-based. + asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1), + Asset::ACCESS_BUFFER); + } else { + asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + } } - if (NULL == asset) { + if (nullptr == asset) { builder->axes.clear(); return false; } const void* buf = asset->getBuffer(false); if (NULL == buf) { - delete asset; builder->axes.clear(); return false; } - sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset)); + sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, + asset.release())); return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); } diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 115d0d5a608b..482d028a67f3 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -576,7 +576,7 @@ namespace PaintGlue { 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 09e37e1a3de6..49a24a30f77e 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -361,7 +361,7 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName code->sdkVersion = sdkVersion; code->javaAssetManager = env->NewGlobalRef(jAssetMgr); - code->assetManager = assetManagerForJavaObject(env, jAssetMgr); + code->assetManager = NdkAssetManagerForJavaObject(env, jAssetMgr); if (obbDir != NULL) { dirStr = env->GetStringUTFChars(obbDir, NULL); diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp new file mode 100644 index 000000000000..c0f151b71c93 --- /dev/null +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "android-base/unique_fd.h" +#include "androidfw/ApkAssets.h" +#include "utils/misc.h" + +#include "core_jni_helpers.h" +#include "jni.h" +#include "nativehelper/ScopedUtfChars.h" + +using ::android::base::unique_fd; + +namespace android { + +static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, + jboolean force_shared_lib, jboolean overlay) { + ScopedUtfChars path(env, java_path); + if (path.c_str() == nullptr) { + return 0; + } + + std::unique_ptr<const ApkAssets> apk_assets; + if (overlay) { + apk_assets = ApkAssets::LoadOverlay(path.c_str(), system); + } else if (force_shared_lib) { + apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system); + } else { + apk_assets = ApkAssets::Load(path.c_str(), system); + } + + if (apk_assets == nullptr) { + std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str()); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } + return reinterpret_cast<jlong>(apk_assets.release()); +} + +static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor, + jstring friendly_name, jboolean system, jboolean force_shared_lib) { + ScopedUtfChars friendly_name_utf8(env, friendly_name); + if (friendly_name_utf8.c_str() == nullptr) { + return 0; + } + + int fd = jniGetFDFromFileDescriptor(env, file_descriptor); + if (fd < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); + return 0; + } + + unique_fd dup_fd(::dup(fd)); + if (dup_fd < 0) { + jniThrowIOException(env, errno); + return 0; + } + + std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), + friendly_name_utf8.c_str(), + system, force_shared_lib); + if (apk_assets == nullptr) { + std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d", + friendly_name_utf8.c_str(), dup_fd.get()); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } + return reinterpret_cast<jlong>(apk_assets.release()); +} + +static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + delete reinterpret_cast<ApkAssets*>(ptr); +} + +static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return env->NewStringUTF(apk_assets->GetPath().c_str()); +} + +static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool()); +} + +static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + (void)apk_assets; + return JNI_TRUE; +} + +static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { + ScopedUtfChars path_utf8(env, file_name); + if (path_utf8.c_str() == nullptr) { + return 0; + } + + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(), + Asset::AccessMode::ACCESS_RANDOM); + if (asset == nullptr) { + jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str()); + return 0; + } + + // DynamicRefTable is only needed when looking up resource references. Opening an XML file + // directly from an ApkAssets has no notion of proper resource references. + std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/); + status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); + asset.reset(); + + if (err != NO_ERROR) { + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + return 0; + } + return reinterpret_cast<jlong>(xml_tree.release()); +} + +// JNI registration. +static const JNINativeMethod gApkAssetsMethods[] = { + {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad}, + {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J", + (void*)NativeLoadFromFd}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, + {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, + {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, + {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, +}; + +int register_android_content_res_ApkAssets(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods, + arraysize(gApkAssetsMethods)); +} + +} // namespace android diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 6b961f54cf88..06de5daab7d2 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -573,6 +573,11 @@ static void freeTextLayoutCaches(JNIEnv* env, jobject) { minikin::Layout::purgeCaches(); } +static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) { + Canvas::setCompatibilityVersion(apiLevel); +} + + }; // namespace CanvasJNI static const JNINativeMethod gMethods[] = { @@ -580,6 +585,7 @@ static const JNINativeMethod gMethods[] = { {"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 16591685848e..e8ef349f5b7a 100644 --- a/core/jni/android_os_VintfObject.cpp +++ b/core/jni/android_os_VintfObject.cpp @@ -32,10 +32,13 @@ static jclass gString; 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 @@ static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) { 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 @@ static const JNINativeMethod gVintfObjectMethods[] = { {"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 @@ int register_android_os_VintfObject(JNIEnv* env) 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 683b4c490ec3..2334e03031e7 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 "android-base/logging.h" +#include "android-base/properties.h" +#include "android-base/stringprintf.h" +#include "android_runtime/android_util_AssetManager.h" +#include "android_runtime/AndroidRuntime.h" +#include "android_util_Binder.h" #include "androidfw/Asset.h" #include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeResolution.h" +#include "androidfw/MutexGuard.h" #include "androidfw/ResourceTypes.h" -#include "android_runtime/AndroidRuntime.h" -#include "android_util_Binder.h" #include "core_jni_helpers.h" #include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedStringChars.h> -#include <nativehelper/ScopedUtfChars.h> +#include "nativehelper/JNIHelp.h" +#include "nativehelper/ScopedPrimitiveArray.h" +#include "nativehelper/ScopedStringChars.h" +#include "nativehelper/ScopedUtfChars.h" #include "utils/Log.h" -#include "utils/misc.h" #include "utils/String8.h" +#include "utils/misc.h" extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap); extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap); +using ::android::base::StringPrintf; namespace android { -static const bool kThrowOnBadId = false; - // ---------------------------------------------------------------------------- -static struct typedvalue_offsets_t -{ - jfieldID mType; - jfieldID mData; - jfieldID mString; - jfieldID mAssetCookie; - jfieldID mResourceId; - jfieldID mChangingConfigurations; - jfieldID mDensity; +static struct typedvalue_offsets_t { + jfieldID mType; + jfieldID mData; + jfieldID mString; + jfieldID mAssetCookie; + jfieldID mResourceId; + jfieldID mChangingConfigurations; + jfieldID mDensity; } gTypedValueOffsets; -static struct assetfiledescriptor_offsets_t -{ - jfieldID mFd; - jfieldID mStartOffset; - jfieldID mLength; +static struct assetfiledescriptor_offsets_t { + jfieldID mFd; + jfieldID mStartOffset; + jfieldID mLength; } gAssetFileDescriptorOffsets; -static struct assetmanager_offsets_t -{ - jfieldID mObject; +static struct assetmanager_offsets_t { + jfieldID mObject; } gAssetManagerOffsets; -static struct sparsearray_offsets_t -{ - jclass classObject; - jmethodID constructor; - jmethodID put; +static struct { + jfieldID native_ptr; +} gApkAssetsFields; + +static struct sparsearray_offsets_t { + jclass classObject; + jmethodID constructor; + jmethodID put; } gSparseArrayOffsets; -static struct configuration_offsets_t -{ - jclass classObject; - jmethodID constructor; - jfieldID mSmallestScreenWidthDpOffset; - jfieldID mScreenWidthDpOffset; - jfieldID mScreenHeightDpOffset; +static struct configuration_offsets_t { + jclass classObject; + jmethodID constructor; + jfieldID mSmallestScreenWidthDpOffset; + jfieldID mScreenWidthDpOffset; + jfieldID mScreenHeightDpOffset; } gConfigurationOffsets; -jclass g_stringClass = NULL; +jclass g_stringClass = nullptr; // ---------------------------------------------------------------------------- -static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table, - const Res_value& value, uint32_t ref, ssize_t block, - uint32_t typeSpecFlags, ResTable_config* config = NULL); - -jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table, - const Res_value& value, uint32_t ref, ssize_t block, - uint32_t typeSpecFlags, ResTable_config* config) -{ - env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType); - env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie, - static_cast<jint>(table->getTableCookie(block))); - env->SetIntField(outValue, gTypedValueOffsets.mData, value.data); - env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL); - env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref); - env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations, - typeSpecFlags); - if (config != NULL) { - env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density); - } - return block; +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +constexpr inline static jint ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<jint>(cookie + 1) : -1; } -// This is called by zygote (running as user root) as part of preloadResources. -static void verifySystemIdmaps() -{ - pid_t pid; - char system_id[10]; - - snprintf(system_id, sizeof(system_id), "%d", AID_SYSTEM); - - switch (pid = fork()) { - case -1: - ALOGE("failed to fork for idmap: %s", strerror(errno)); - break; - case 0: // child - { - struct __user_cap_header_struct capheader; - struct __user_cap_data_struct capdata; - - memset(&capheader, 0, sizeof(capheader)); - memset(&capdata, 0, sizeof(capdata)); - - capheader.version = _LINUX_CAPABILITY_VERSION; - capheader.pid = 0; - - if (capget(&capheader, &capdata) != 0) { - ALOGE("capget: %s\n", strerror(errno)); - exit(1); - } - - capdata.effective = capdata.permitted; - if (capset(&capheader, &capdata) != 0) { - ALOGE("capset: %s\n", strerror(errno)); - exit(1); - } - - if (setgid(AID_SYSTEM) != 0) { - ALOGE("setgid: %s\n", strerror(errno)); - exit(1); - } - - if (setuid(AID_SYSTEM) != 0) { - ALOGE("setuid: %s\n", strerror(errno)); - exit(1); - } - - // Generic idmap parameters - const char* argv[8]; - int argc = 0; - struct stat st; - - memset(argv, NULL, sizeof(argv)); - argv[argc++] = AssetManager::IDMAP_BIN; - argv[argc++] = "--scan"; - argv[argc++] = AssetManager::TARGET_PACKAGE_NAME; - argv[argc++] = AssetManager::TARGET_APK_PATH; - argv[argc++] = AssetManager::IDMAP_DIR; - - // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined, - // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR. - char subdir[PROP_VALUE_MAX]; - int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir); - if (len > 0) { - String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir; - if (stat(overlayPath.string(), &st) == 0) { - argv[argc++] = overlayPath.string(); - } - } - if (stat(AssetManager::OVERLAY_DIR, &st) == 0) { - argv[argc++] = AssetManager::OVERLAY_DIR; - } - - if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) { - argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR; - } - - // Finally, invoke idmap (if any overlay directory exists) - if (argc > 5) { - execv(AssetManager::IDMAP_BIN, (char* const*)argv); - ALOGE("failed to execv for idmap: %s", strerror(errno)); - exit(1); // should never get here - } else { - exit(0); - } - } - break; - default: // parent - waitpid(pid, NULL, 0); - break; - } +constexpr inline static ApkAssetsCookie JavaCookieToApkAssetsCookie(jint cookie) { + return cookie > 0 ? static_cast<ApkAssetsCookie>(cookie - 1) : kInvalidCookie; } - -// ---------------------------------------------------------------------------- - -// this guy is exported to other jni routines -AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj) -{ - jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject); - AssetManager* am = reinterpret_cast<AssetManager*>(amHandle); - if (am != NULL) { - return am; - } - jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!"); - return NULL; -} - -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); +// This is called by zygote (running as user root) as part of preloadResources. +static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { + switch (pid_t pid = fork()) { + case -1: + PLOG(ERROR) << "failed to fork for idmap"; + break; + + // child + case 0: { + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata; + + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + + capheader.version = _LINUX_CAPABILITY_VERSION; + capheader.pid = 0; + + if (capget(&capheader, &capdata) != 0) { + PLOG(ERROR) << "capget"; + exit(1); + } + + capdata.effective = capdata.permitted; + if (capset(&capheader, &capdata) != 0) { + PLOG(ERROR) << "capset"; + exit(1); + } + + if (setgid(AID_SYSTEM) != 0) { + PLOG(ERROR) << "setgid"; + exit(1); + } + + if (setuid(AID_SYSTEM) != 0) { + PLOG(ERROR) << "setuid"; + exit(1); + } + + // Generic idmap parameters + const char* argv[8]; + int argc = 0; + struct stat st; + + memset(argv, 0, sizeof(argv)); + argv[argc++] = AssetManager::IDMAP_BIN; + argv[argc++] = "--scan"; + argv[argc++] = AssetManager::TARGET_PACKAGE_NAME; + argv[argc++] = AssetManager::TARGET_APK_PATH; + argv[argc++] = AssetManager::IDMAP_DIR; + + // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined, + // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR. + std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY, + ""); + if (!overlay_theme_path.empty()) { + overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path; + if (stat(overlay_theme_path.c_str(), &st) == 0) { + argv[argc++] = overlay_theme_path.c_str(); + } + } + + if (stat(AssetManager::OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::OVERLAY_DIR; + } + + if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR; + } + + // Finally, invoke idmap (if any overlay directory exists) + if (argc > 5) { + execv(AssetManager::IDMAP_BIN, (char* const*)argv); + PLOG(ERROR) << "failed to execv for idmap"; + exit(1); // should never get here + } else { + exit(0); + } + } break; + + // parent + default: + waitpid(pid, nullptr, 0); + break; + } } -static 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 jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref, + uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) { + env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType); + env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie, + ApkAssetsCookieToJavaCookie(cookie)); + env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data); + env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr); + env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref); + env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags); + if (config != nullptr) { + env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density); + } + return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie)); } -static jobject 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); +// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance. +struct GuardedAssetManager : public ::AAssetManager { + Guarded<AssetManager2> guarded_assetmanager; +}; - return returnParcelFileDescriptor(env, a, outOffsets); +::AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) { + jlong assetmanager_handle = env->GetLongField(jassetmanager, gAssetManagerOffsets.mObject); + ::AAssetManager* am = reinterpret_cast<::AAssetManager*>(assetmanager_handle); + if (am == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!"); + return nullptr; + } + return am; } -static jlong android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName, - jint mode) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return -1; - } - - if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM - && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); - return -1; - } - - Asset* a = cookie - ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), - (Asset::AccessMode)mode) - : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return -1; - } - - //printf("Created Asset Stream: %p\n", a); - - return reinterpret_cast<jlong>(a); +Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) { + if (assetmanager == nullptr) { + return nullptr; + } + return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager; } -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); +Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) { + return AssetManagerForNdkAssetManager(NdkAssetManagerForJavaObject(env, jassetmanager)); } -static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz, - jstring fileName) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - AssetDir* dir = am->openDir(fileName8.c_str()); - - if (dir == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - size_t N = dir->getFileCount(); - - jobjectArray array = env->NewObjectArray(dir->getFileCount(), - g_stringClass, NULL); - if (array == NULL) { - delete dir; - return NULL; - } - - for (size_t i=0; i<N; i++) { - const String8& name = dir->getFileName(i); - jstring str = env->NewStringUTF(name.string()); - if (str == NULL) { - delete dir; - return NULL; - } - env->SetObjectArrayElement(array, i, str); - env->DeleteLocalRef(str); - } - - delete dir; - - return array; +static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) { + return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr)); } -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 jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset, + jlongArray out_offsets) { + off64_t start_offset, length; + int fd = asset->openFileDescriptor(&start_offset, &length); + asset.reset(); + + if (fd < 0) { + jniThrowException(env, "java/io/FileNotFoundException", + "This file can not be opened as a file descriptor; it is probably " + "compressed"); + return nullptr; + } + + jlong* offsets = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(out_offsets, 0)); + if (offsets == nullptr) { + close(fd); + return nullptr; + } + + offsets[0] = start_offset; + offsets[1] = length; + + env->ReleasePrimitiveArrayCritical(out_offsets, offsets, 0); + + jobject file_desc = jniCreateFileDescriptor(env, fd); + if (file_desc == nullptr) { + close(fd); + return nullptr; + } + return newParcelFileDescriptor(env, file_desc); } -static 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 NativeGetGlobalAssetCount(JNIEnv* /*env*/, jobject /*clazz*/) { + return Asset::getGlobalCount(); } -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 jobject NativeGetAssetAllocations(JNIEnv* env, jobject /*clazz*/) { + String8 alloc = Asset::getAssetAllocations(); + if (alloc.length() <= 0) { + return nullptr; + } + return env->NewStringUTF(alloc.string()); } -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 jint NativeGetGlobalAssetManagerCount(JNIEnv* /*env*/, jobject /*clazz*/) { + // TODO(adamlesinski): Switch to AssetManager2. + return AssetManager::getGlobalCount(); } -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 NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) { + // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and + // AssetManager2 in a contiguous block (GuardedAssetManager). + return reinterpret_cast<jlong>(new GuardedAssetManager()); } -static jlong android_content_AssetManager_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 void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + delete reinterpret_cast<GuardedAssetManager*>(ptr); } -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; +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; } - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; + jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr); + if (env->ExceptionCheck()) { + return; } + apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr)); + } - int32_t cookie; - bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib); - - return (res) ? static_cast<jint>(cookie) : 0; + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + assetmanager->SetApkAssets(apk_assets, invalidate_caches); } -static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz, - jstring idmapPath) -{ - ScopedUtfChars idmapPath8(env, idmapPath); - if (idmapPath8.c_str() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - int32_t cookie; - bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie); - - return (res) ? (jint)cookie : 0; +static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc, + jstring locale, jint orientation, jint touchscreen, jint density, + jint keyboard, jint keyboard_hidden, jint navigation, + jint screen_width, jint screen_height, + jint smallest_screen_width_dp, jint screen_width_dp, + jint screen_height_dp, jint screen_layout, jint ui_mode, + jint color_mode, jint major_version) { + ResTable_config configuration; + memset(&configuration, 0, sizeof(configuration)); + configuration.mcc = static_cast<uint16_t>(mcc); + configuration.mnc = static_cast<uint16_t>(mnc); + configuration.orientation = static_cast<uint8_t>(orientation); + configuration.touchscreen = static_cast<uint8_t>(touchscreen); + configuration.density = static_cast<uint16_t>(density); + configuration.keyboard = static_cast<uint8_t>(keyboard); + configuration.inputFlags = static_cast<uint8_t>(keyboard_hidden); + configuration.navigation = static_cast<uint8_t>(navigation); + configuration.screenWidth = static_cast<uint16_t>(screen_width); + configuration.screenHeight = static_cast<uint16_t>(screen_height); + configuration.smallestScreenWidthDp = static_cast<uint16_t>(smallest_screen_width_dp); + configuration.screenWidthDp = static_cast<uint16_t>(screen_width_dp); + configuration.screenHeightDp = static_cast<uint16_t>(screen_height_dp); + configuration.screenLayout = static_cast<uint8_t>(screen_layout); + configuration.uiMode = static_cast<uint8_t>(ui_mode); + configuration.colorMode = static_cast<uint8_t>(color_mode); + configuration.sdkVersion = static_cast<uint16_t>(major_version); + + if (locale != nullptr) { + ScopedUtfChars locale_utf8(env, locale); + CHECK(locale_utf8.c_str() != nullptr); + configuration.setBcp47Locale(locale_utf8.c_str()); + } + + // Constants duplicated from Java class android.content.res.Configuration. + static const jint kScreenLayoutRoundMask = 0x300; + static const jint kScreenLayoutRoundShift = 8; + + // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer + // in C++. We must extract the round qualifier out of the Java screenLayout and put it + // into screenLayout2. + configuration.screenLayout2 = + static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + assetmanager->SetConfiguration(configuration); } -static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jstring debugPathName, - jboolean appAsLib) -{ - ScopedUtfChars debugPathName8(env, debugPathName); +static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (fd < 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); - return 0; - } + jobject sparse_array = + env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor); - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } + if (sparse_array == nullptr) { + // An exception is pending. + return nullptr; + } - int dupfd = ::dup(fd); - if (dupfd < 0) { - jniThrowIOException(env, errno); - return 0; + assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) { + jstring jpackage_name = env->NewStringUTF(package_name.c_str()); + if (jpackage_name == nullptr) { + // An exception is pending. + return; } - int32_t cookie; - bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib); - - return (res) ? static_cast<jint>(cookie) : 0; + env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id), + jpackage_name); + }); + return sparse_array; } -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 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; + } -static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales) -{ - Vector<String8> locales; + 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; + } - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } + const size_t file_count = asset_dir->getFileCount(); - am->getLocales(&locales, includeSystemLocales); + jobjectArray array = env->NewObjectArray(file_count, g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } - const int N = locales.size(); + for (size_t i = 0; i < file_count; i++) { + jstring java_string = env->NewStringUTF(asset_dir->getFileName(i).string()); - jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL); - if (result == NULL) { - return NULL; + // Check for errors creating the strings (if malformed or no memory). + if (env->ExceptionCheck()) { + return nullptr; } - for (int i=0; i<N; i++) { - jstring str = env->NewStringUTF(locales[i].string()); - if (str == NULL) { - return NULL; - } - env->SetObjectArrayElement(result, i, str); - env->DeleteLocalRef(str); - } + env->SetObjectArrayElement(array, i, java_string); - return result; + // If we have a large amount of string in our array, we might overflow the + // local reference table of the VM. + env->DeleteLocalRef(java_string); + } + return array; } -static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) -{ - return getLocales(env, clazz, true /* include system locales */); +static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path, + jint access_mode) { + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM && + access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset = + assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode)); + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + return reinterpret_cast<jlong>(asset.release()); } -static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz) -{ - return getLocales(env, clazz, false /* don't include system locales */); +static jobject NativeOpenAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path, + jlongArray out_offsets) { + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM); + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return nullptr; + } + return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets); } -static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) { - jobject result = env->NewObject(gConfigurationOffsets.classObject, - gConfigurationOffsets.constructor); - if (result == NULL) { - return NULL; - } - - env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset, - config.smallestScreenWidthDp); - env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); - env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); - - return result; +static jlong NativeOpenNonAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path, jint access_mode) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM && + access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, + static_cast<Asset::AccessMode>(access_mode)); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), + static_cast<Asset::AccessMode>(access_mode)); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + return reinterpret_cast<jlong>(asset.release()); } -static jobjectArray getSizeConfigurationsInternal(JNIEnv* env, - const Vector<ResTable_config>& configs) { - const int N = configs.size(); - jobjectArray result = env->NewObjectArray(N, gConfigurationOffsets.classObject, NULL); - if (result == NULL) { - return NULL; - } - - for (int i=0; i<N; i++) { - jobject config = constructConfigurationObject(env, configs[i]); - if (config == NULL) { - env->DeleteLocalRef(result); - return NULL; - } - - env->SetObjectArrayElement(result, i, config); - env->DeleteLocalRef(config); - } - - return result; +static jobject NativeOpenNonAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path, jlongArray out_offsets) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return nullptr; + } + return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets); } -static jobjectArray android_content_AssetManager_getSizeConfigurations(JNIEnv* env, jobject clazz) { - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - const ResTable& res(am->getResources()); - Vector<ResTable_config> configs; - res.getConfigurations(&configs, false /* ignoreMipmap */, true /* ignoreAndroidPackage */); - - return getSizeConfigurationsInternal(env, configs); +static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + + // May be nullptr. + const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie); + + std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table); + status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); + asset.reset(); + + if (err != NO_ERROR) { + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + return 0; + } + return reinterpret_cast<jlong>(xml_tree.release()); } -static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz, - jint mcc, jint mnc, - jstring locale, jint orientation, - jint touchscreen, jint density, - jint keyboard, jint keyboardHidden, - jint navigation, - jint screenWidth, jint screenHeight, - jint smallestScreenWidthDp, - jint screenWidthDp, jint screenHeightDp, - jint screenLayout, jint uiMode, - jint colorMode, jint sdkVersion) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return; - } - - ResTable_config config; - memset(&config, 0, sizeof(config)); - - const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL; - - // Constants duplicated from Java class android.content.res.Configuration. - static const jint kScreenLayoutRoundMask = 0x300; - static const jint kScreenLayoutRoundShift = 8; - - config.mcc = (uint16_t)mcc; - config.mnc = (uint16_t)mnc; - config.orientation = (uint8_t)orientation; - config.touchscreen = (uint8_t)touchscreen; - config.density = (uint16_t)density; - config.keyboard = (uint8_t)keyboard; - config.inputFlags = (uint8_t)keyboardHidden; - config.navigation = (uint8_t)navigation; - config.screenWidth = (uint16_t)screenWidth; - config.screenHeight = (uint16_t)screenHeight; - config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp; - config.screenWidthDp = (uint16_t)screenWidthDp; - config.screenHeightDp = (uint16_t)screenHeightDp; - config.screenLayout = (uint8_t)screenLayout; - config.uiMode = (uint8_t)uiMode; - config.colorMode = (uint8_t)colorMode; - config.sdkVersion = (uint16_t)sdkVersion; - config.minorVersion = 0; - - // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer - // in C++. We must extract the round qualifier out of the Java screenLayout and put it - // into screenLayout2. - config.screenLayout2 = - (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); - - am->setConfiguration(config, locale8); - - if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8); +static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jshort density, jobject typed_value, + jboolean resolve_references) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Res_value value; + ResTable_config selected_config; + uint32_t flags; + ApkAssetsCookie cookie = + assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/, + static_cast<uint16_t>(density), &value, &selected_config, &flags); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t ref = static_cast<uint32_t>(resid); + if (resolve_references) { + cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + } + return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value); } -static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz, - jstring name, - jstring defType, - jstring defPackage) -{ - ScopedStringChars name16(env, name); - if (name16.get() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - const char16_t* defType16 = reinterpret_cast<const char16_t*>(defType) - ? reinterpret_cast<const char16_t*>(env->GetStringChars(defType, NULL)) - : NULL; - jsize defTypeLen = defType - ? env->GetStringLength(defType) : 0; - const char16_t* defPackage16 = reinterpret_cast<const char16_t*>(defPackage) - ? reinterpret_cast<const char16_t*>(env->GetStringChars(defPackage, - NULL)) - : NULL; - jsize defPackageLen = defPackage - ? env->GetStringLength(defPackage) : 0; - - jint ident = am->getResources().identifierForName( - reinterpret_cast<const char16_t*>(name16.get()), name16.size(), - defType16, defTypeLen, defPackage16, defPackageLen); - - if (defPackage16) { - env->ReleaseStringChars(defPackage, - reinterpret_cast<const jchar*>(defPackage16)); - } - if (defType16) { - env->ReleaseStringChars(defType, - reinterpret_cast<const jchar*>(defType16)); - } - - return ident; +static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jint bag_entry_id, jobject typed_value) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t type_spec_flags = bag->type_spec_flags; + ApkAssetsCookie cookie = kInvalidCookie; + const Res_value* bag_value = nullptr; + for (const ResolvedBag::Entry& entry : bag) { + if (entry.key == static_cast<uint32_t>(bag_entry_id)) { + cookie = entry.cookie; + bag_value = &entry.value; + + // Keep searching (the old implementation did that). + } + } + + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + Res_value value = *bag_value; + uint32_t ref = static_cast<uint32_t>(resid); + ResTable_config selected_config; + cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &type_spec_flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + return CopyValue(env, cookie, value, ref, type_spec_flags, nullptr, typed_value); } -static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - String16 str; - if (name.package != NULL) { - str.setTo(name.package, name.packageLen); - } - if (name.type8 != NULL || name.type != NULL) { - if (str.size() > 0) { - char16_t div = ':'; - str.append(&div, 1); - } - if (name.type8 != NULL) { - str.append(String16(name.type8, name.typeLen)); - } else { - str.append(name.type, name.typeLen); - } - } - if (name.name8 != NULL || name.name != NULL) { - if (str.size() > 0) { - char16_t div = '/'; - str.append(&div, 1); - } - if (name.name8 != NULL) { - str.append(String16(name.name8, name.nameLen)); - } else { - str.append(name.name, name.nameLen); - } - } - - return env->NewString((const jchar*)str.string(), str.size()); +static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count); + if (env->ExceptionCheck()) { + return nullptr; + } + + for (uint32_t i = 0; i < bag->entry_count; i++) { + jint attr_resid = bag->entries[i].key; + env->SetIntArrayRegion(array, i, 1, &attr_resid); + } + return array; } -static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - if (name.package != NULL) { - return env->NewString((const jchar*)name.package, name.packageLen); - } - - return NULL; +static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jobjectArray array = env->NewObjectArray(bag->entry_count, g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } + + for (uint32_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + + // Resolve any references to their final value. + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return nullptr; + } + + if (value.dataType == Res_value::TYPE_STRING) { + const ApkAssets* apk_assets = assetmanager->GetApkAssets()[cookie]; + const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool(); + + jstring java_string = nullptr; + size_t str_len; + const char* str_utf8 = pool->string8At(value.data, &str_len); + if (str_utf8 != nullptr) { + java_string = env->NewStringUTF(str_utf8); + } else { + const char16_t* str_utf16 = pool->stringAt(value.data, &str_len); + java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16), str_len); + } + + // Check for errors creating the strings (if malformed or no memory). + if (env->ExceptionCheck()) { + return nullptr; + } + + env->SetObjectArrayElement(array, i, java_string); + + // If we have a large amount of string in our array, we might overflow the + // local reference table of the VM. + env->DeleteLocalRef(java_string); + } + } + return array; } -static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - if (name.type8 != NULL) { - return env->NewStringUTF(name.type8); - } - - if (name.type != NULL) { - return env->NewString((const jchar*)name.type, name.typeLen); - } +static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count * 2); + if (array == nullptr) { + return nullptr; + } + + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr)); + if (buffer == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT); + return nullptr; + } + + jint string_index = -1; + if (value.dataType == Res_value::TYPE_STRING) { + string_index = static_cast<jint>(value.data); + } + + buffer[i * 2] = ApkAssetsCookieToJavaCookie(cookie); + buffer[(i * 2) + 1] = string_index; + } + env->ReleasePrimitiveArrayCritical(array, buffer, 0); + return array; +} - return NULL; +static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count); + if (array == nullptr) { + return nullptr; + } + + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr)); + if (buffer == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT); + return nullptr; + } + + if (value.dataType >= Res_value::TYPE_FIRST_INT && value.dataType <= Res_value::TYPE_LAST_INT) { + buffer[i] = static_cast<jint>(value.data); + } + } + env->ReleasePrimitiveArrayCritical(array, buffer, 0); + return array; } -static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } +static jint NativeGetResourceArraySize(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return -1; + } + return static_cast<jint>(bag->entry_count); +} - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } +static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jintArray out_data) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return -1; + } - if (name.name8 != NULL) { - return env->NewStringUTF(name.name8); - } + const jsize out_data_length = env->GetArrayLength(out_data); + if (env->ExceptionCheck()) { + return -1; + } - if (name.name != NULL) { - return env->NewString((const jchar*)name.name, name.nameLen); - } + if (static_cast<jsize>(bag->entry_count) > out_data_length * STYLE_NUM_ENTRIES) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Input array is not large enough"); + return -1; + } - return NULL; + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_data, nullptr)); + if (buffer == nullptr) { + return -1; + } + + jint* cursor = buffer; + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + selected_config.density = 0; + uint32_t flags = bag->type_spec_flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(out_data, buffer, JNI_ABORT); + return -1; + } + + // Deal with the special @null value -- it turns back to TYPE_NULL. + if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { + value.dataType = Res_value::TYPE_NULL; + value.data = Res_value::DATA_NULL_UNDEFINED; + } + + cursor[STYLE_TYPE] = static_cast<jint>(value.dataType); + cursor[STYLE_DATA] = static_cast<jint>(value.data); + cursor[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); + cursor[STYLE_RESOURCE_ID] = static_cast<jint>(ref); + cursor[STYLE_CHANGING_CONFIGURATIONS] = static_cast<jint>(flags); + cursor[STYLE_DENSITY] = static_cast<jint>(selected_config.density); + cursor += STYLE_NUM_ENTRIES; + } + env->ReleasePrimitiveArrayCritical(out_data, buffer, 0); + return static_cast<jint>(bag->entry_count); } -static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, - jint ident, - jshort density, - jobject outValue, - jboolean resolve) -{ - if (outValue == NULL) { - jniThrowNullPointerException(env, "outValue"); - return 0; - } - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - Res_value value; - ResTable_config config; - uint32_t typeSpecFlags; - ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - uint32_t ref = ident; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - } - if (block >= 0) { - return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); - } - - return static_cast<jint>(block); +static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name, + jstring def_type, jstring def_package) { + ScopedUtfChars name_utf8(env, name); + if (name_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + std::string type; + if (def_type != nullptr) { + ScopedUtfChars type_utf8(env, def_type); + CHECK(type_utf8.c_str() != nullptr); + type = type_utf8.c_str(); + } + + std::string package; + if (def_package != nullptr) { + ScopedUtfChars package_utf8(env, def_package); + CHECK(package_utf8.c_str() != nullptr); + package = package_utf8.c_str(); + } + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return static_cast<jint>(assetmanager->GetResourceId(name_utf8.c_str(), type, package)); } -static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz, - jint ident, jint bagEntryId, - jobject outValue, jboolean resolve) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); +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; + } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); + std::string result; + if (name.package != nullptr) { + result.append(name.package, name.package_len); + } - 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++; + if (name.type != nullptr || name.type16 != nullptr) { + if (!result.empty()) { + result += ":"; } - res.unlock(); - - if (block < 0) { - return static_cast<jint>(block); + if (name.type != nullptr) { + result.append(name.type, name.type_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len)); } + } - uint32_t ref = ident; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } + if (name.entry != nullptr || name.entry16 != nullptr) { + if (!result.empty()) { + result += "/"; } - 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; + if (name.entry != nullptr) { + result.append(name.entry, name.entry_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len)); } - return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block)); + } + return env->NewStringUTF(result.c_str()); } -static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz, - jint cookie) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - String8 name(am->getAssetPath(static_cast<int32_t>(cookie))); - if (name.length() == 0) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name"); - return NULL; - } - jstring str = env->NewStringUTF(name.string()); - return str; +static jstring 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 jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - const ResTable& res = am->getResources(); - - jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject, - gSparseArrayOffsets.constructor); - const size_t N = res.getBasePackageCount(); - for (size_t i = 0; i < N; i++) { - const String16 name = res.getBasePackageName(i); - env->CallVoidMethod( - sparseArray, gSparseArrayOffsets.put, - static_cast<jint>(res.getBasePackageId(i)), - env->NewString(reinterpret_cast<const jchar*>(name.string()), - name.size())); - } - return sparseArray; +static jstring 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 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 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 void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz, - jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - delete theme; +static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr, + jboolean exclude_system) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::set<std::string> locales = + assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/); + + jobjectArray array = env->NewObjectArray(locales.size(), g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } + + size_t idx = 0; + for (const std::string& locale : locales) { + jstring java_string = env->NewStringUTF(locale.c_str()); + if (java_string == nullptr) { + return nullptr; + } + env->SetObjectArrayElement(array, idx++, java_string); + env->DeleteLocalRef(java_string); + } + return array; } -static void android_content_AssetManager_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 jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config) { + jobject result = + env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor); + if (result == nullptr) { + return nullptr; + } + + env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset, + config.smallestScreenWidthDp); + env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); + env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); + return result; } -static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz, - jlong destHandle, jlong srcHandle) -{ - ResTable::Theme* dest = reinterpret_cast<ResTable::Theme*>(destHandle); - ResTable::Theme* src = reinterpret_cast<ResTable::Theme*>(srcHandle); - dest->setTo(*src); -} +static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::set<ResTable_config> configurations = + assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/); -static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - theme->clear(); -} + jobjectArray array = + env->NewObjectArray(configurations.size(), gConfigurationOffsets.classObject, nullptr); + if (array == nullptr) { + return nullptr; + } -static jint android_content_AssetManager_loadThemeAttributeValue( - JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - const ResTable& res(theme->getResTable()); - - Res_value value; - // XXX value could be different in different configs! - uint32_t typeSpecFlags = 0; - ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags); - uint32_t ref = 0; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } + size_t idx = 0; + for (const ResTable_config& configuration : configurations) { + jobject java_configuration = ConstructConfigurationObject(env, configuration); + if (java_configuration == nullptr) { + return nullptr; } - return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block; -} -static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz, - jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - return theme->getChangingConfigurations(); + env->SetObjectArrayElement(array, idx++, java_configuration); + env->DeleteLocalRef(java_configuration); + } + return array; } -static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, - jlong themeHandle, jint pri, - jstring tag, jstring prefix) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - const ResTable& res(theme->getResTable()); - (void)res; - - // XXX Need to use params. - theme->dumpToLog(); +static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr, + jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); + uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr); + uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr); + + jsize attrs_len = env->GetArrayLength(java_attrs); + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return; + } + + ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr), + static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len, + out_values, out_indices); + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); } -static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz, - jlong themeToken, - jint defStyleAttr, - jint defStyleRes, - jintArray inValues, - jintArray attrs, - jintArray outValues, - jintArray outIndices) -{ - if (themeToken == 0) { - jniThrowNullPointerException(env, "theme token"); - return JNI_FALSE; - } - if (attrs == NULL) { - jniThrowNullPointerException(env, "attrs"); - return JNI_FALSE; - } - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - const jsize NI = env->GetArrayLength(attrs); - const jsize NV = env->GetArrayLength(outValues); - if (NV < (NI*STYLE_NUM_ENTRIES)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); +static jboolean NativeResolveAttrs(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint def_style_attr, jint def_style_resid, jintArray java_values, + jintArray java_attrs, jintArray out_java_values, + jintArray out_java_indices) { + const jsize attrs_len = env->GetArrayLength(java_attrs); + const jsize out_values_len = env->GetArrayLength(out_java_values); + if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small"); + return JNI_FALSE; + } + + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return JNI_FALSE; + } + + jint* values = nullptr; + jsize values_len = 0; + if (java_values != nullptr) { + values_len = env->GetArrayLength(java_values); + values = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_values, nullptr)); + if (values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return JNI_FALSE; + } + } + + jint* out_values = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr)); + if (out_values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + return JNI_FALSE; + } + + jint* out_indices = nullptr; + if (out_java_indices != nullptr) { + jsize out_indices_len = env->GetArrayLength(out_java_indices); + if (out_indices_len > attrs_len) { + out_indices = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr)); + if (out_indices == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT); return JNI_FALSE; - } + } + } + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + bool result = ResolveAttrs( + theme, static_cast<uint32_t>(def_style_attr), static_cast<uint32_t>(def_style_resid), + reinterpret_cast<uint32_t*>(values), values_len, reinterpret_cast<uint32_t*>(attrs), + attrs_len, reinterpret_cast<uint32_t*>(out_values), reinterpret_cast<uint32_t*>(out_indices)); + if (out_indices != nullptr) { + env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0); + } + + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return result ? JNI_TRUE : JNI_FALSE; +} - jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); - if (src == NULL) { +static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jlong xml_parser_ptr, jintArray java_attrs, + jintArray out_java_values, jintArray out_java_indices) { + const jsize attrs_len = env->GetArrayLength(java_attrs); + const jsize out_values_len = env->GetArrayLength(out_java_values); + if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small"); + return JNI_FALSE; + } + + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return JNI_FALSE; + } + + jint* out_values = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr)); + if (out_values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return JNI_FALSE; + } + + jint* out_indices = nullptr; + if (out_java_indices != nullptr) { + jsize out_indices_len = env->GetArrayLength(out_java_indices); + if (out_indices_len > attrs_len) { + out_indices = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr)); + if (out_indices == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT); return JNI_FALSE; + } } + } - jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0); - const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues); + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - if (baseDest == NULL) { - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return JNI_FALSE; - } - - jint* indices = NULL; - if (outIndices != NULL) { - if (env->GetArrayLength(outIndices) > NI) { - indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); - } - } + bool result = RetrieveAttributes(assetmanager.get(), xml_parser, + reinterpret_cast<uint32_t*>(attrs), attrs_len, + reinterpret_cast<uint32_t*>(out_values), + reinterpret_cast<uint32_t*>(out_indices)); - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); - bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes, - (uint32_t*) srcValues, NSV, - (uint32_t*) src, NI, - (uint32_t*) baseDest, - (uint32_t*) indices); - - if (indices != NULL) { - env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); - } - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0); - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return result ? JNI_TRUE : JNI_FALSE; + if (out_indices != nullptr) { + env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0); + } + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0); + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return result ? JNI_TRUE : JNI_FALSE; } -static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken, - jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length, - jlong outValuesAddress, jlong outIndicesAddress) { - jint* attrs = env->GetIntArrayElements(attrsObj, 0); - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); - ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken); - uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress)); - uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress)); - ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes, - reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices); - env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT); +static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return reinterpret_cast<jlong>(assetmanager->NewTheme().release()); } -static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz, - jlong xmlParserToken, - jintArray attrs, - jintArray outValues, - jintArray outIndices) -{ - if (xmlParserToken == 0) { - jniThrowNullPointerException(env, "xmlParserToken"); - return JNI_FALSE; - } - if (attrs == NULL) { - jniThrowNullPointerException(env, "attrs"); - return JNI_FALSE; - } - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_FALSE; - } - const ResTable& res(am->getResources()); - ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken; - - const jsize NI = env->GetArrayLength(attrs); - const jsize NV = env->GetArrayLength(outValues); - if (NV < (NI*STYLE_NUM_ENTRIES)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); - return JNI_FALSE; - } - - jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); - if (src == NULL) { - return JNI_FALSE; - } - - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - if (baseDest == NULL) { - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return JNI_FALSE; - } - - jint* indices = NULL; - if (outIndices != NULL) { - if (env->GetArrayLength(outIndices) > NI) { - indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); - } - } - - bool result = RetrieveAttributes(&res, xmlParser, - (uint32_t*) src, NI, - (uint32_t*) baseDest, - (uint32_t*) indices); - - if (indices != NULL) { - env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); - } - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return result ? JNI_TRUE : JNI_FALSE; +static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) { + delete reinterpret_cast<Theme*>(theme_ptr); } -static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz, - jint id) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - res.lock(); - const ResTable::bag_entry* defStyleEnt = NULL; - ssize_t bagOff = res.getBagLocked(id, &defStyleEnt); - res.unlock(); - - return static_cast<jint>(bagOff); +static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint resid, jboolean force) { + // AssetManager is accessed via the theme, so grab an explicit lock here. + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + theme->ApplyStyle(static_cast<uint32_t>(resid), force); + + // TODO(adamlesinski): Consider surfacing exception when result is failure. + // CTS currently expects no exceptions from this method. + // std::string error_msg = StringPrintf("Failed to apply style 0x%08x to theme", resid); + // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); } -static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz, - jint id, - jintArray outValues) -{ - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_FALSE; - } - const ResTable& res(am->getResources()); - ResTable_config config; - Res_value value; - ssize_t block; - - const jsize NV = env->GetArrayLength(outValues); - - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - jint* dest = baseDest; - if (dest == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", ""); - return JNI_FALSE; - } - - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - - const ResTable::bag_entry* arrayEnt = NULL; - uint32_t arrayTypeSetFlags = 0; - ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags); - const ResTable::bag_entry* endArrayEnt = arrayEnt + - (bagOff >= 0 ? bagOff : 0); - - int i = 0; - uint32_t typeSetFlags; - while (i < NV && arrayEnt < endArrayEnt) { - block = arrayEnt->stringBlock; - typeSetFlags = arrayTypeSetFlags; - config.density = 0; - value = arrayEnt->map.value; - - uint32_t resid = 0; - if (value.dataType != Res_value::TYPE_NULL) { - // Take care of resolving the found resource to its final value. - //printf("Resolving attribute reference\n"); - ssize_t newBlock = res.resolveReference(&value, block, &resid, - &typeSetFlags, &config); - if (kThrowOnBadId) { - if (newBlock == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return JNI_FALSE; - } - } - if (newBlock >= 0) block = newBlock; - } - - // Deal with the special @null value -- it turns back to TYPE_NULL. - if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - value.dataType = Res_value::TYPE_NULL; - value.data = Res_value::DATA_NULL_UNDEFINED; - } - - //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); - - // Write the final value back to Java. - dest[STYLE_TYPE] = value.dataType; - dest[STYLE_DATA] = value.data; - dest[STYLE_ASSET_COOKIE] = reinterpret_cast<jint>(res.getTableCookie(block)); - dest[STYLE_RESOURCE_ID] = resid; - dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; - dest[STYLE_DENSITY] = config.density; - dest += STYLE_NUM_ENTRIES; - i+= STYLE_NUM_ENTRIES; - arrayEnt++; - } - - i /= STYLE_NUM_ENTRIES; - - res.unlock(); - - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - - return i; +static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr, + jlong src_theme_ptr) { + Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr); + Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr); + if (!dst_theme->SetTo(*src_theme)) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Themes are from different AssetManagers"); + } } -static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return 0; - } - - int32_t assetCookie = static_cast<int32_t>(cookie); - Asset* a = assetCookie - ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return 0; - } - - const DynamicRefTable* dynamicRefTable = - am->getResources().getDynamicRefTableForCookie(assetCookie); - ResXMLTree* block = new ResXMLTree(dynamicRefTable); - status_t err = block->setTo(a->getBuffer(true), a->getLength(), true); - a->close(); - delete a; - - if (err != NO_ERROR) { - jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); - return 0; - } - - return reinterpret_cast<jlong>(block); +static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) { + reinterpret_cast<Theme*>(theme_ptr)->Clear(); } -static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N * 2); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) { - jint stringIndex = -1; - jint stringBlock = 0; - value = bag->map.value; - - // Take care of resolving the found resource to its final value. - stringBlock = res.resolveReference(&value, bag->stringBlock, NULL); - if (value.dataType == Res_value::TYPE_STRING) { - stringIndex = value.data; - } - - if (kThrowOnBadId) { - if (stringBlock == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - - //todo: It might be faster to allocate a C array to contain - // the blocknums and indices, put them in there and then - // do just one SetIntArrayRegion() - env->SetIntArrayRegion(array, j, 1, &stringBlock); - env->SetIntArrayRegion(array, j + 1, 1, &stringIndex); - j = j + 2; - } - res.unlockBag(startOfBag); - return array; +static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint resid, jobject typed_value, + jboolean resolve_references) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + Res_value value; + uint32_t flags; + ApkAssetsCookie cookie = theme->GetAttribute(static_cast<uint32_t>(resid), &value, &flags); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t ref = 0u; + if (resolve_references) { + ResTable_config selected_config; + cookie = + theme->GetAssetManager()->ResolveReference(cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + } + return CopyValue(env, cookie, value, ref, flags, nullptr, typed_value); } -static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL); - if (env->ExceptionCheck()) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - size_t strLen = 0; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - value = bag->map.value; - jstring str = NULL; - - // Take care of resolving the found resource to its final value. - ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - if (value.dataType == Res_value::TYPE_STRING) { - const ResStringPool* pool = res.getTableStringBlock(block); - const char* str8 = pool->string8At(value.data, &strLen); - if (str8 != NULL) { - str = env->NewStringUTF(str8); - } else { - const char16_t* str16 = pool->stringAt(value.data, &strLen); - str = env->NewString(reinterpret_cast<const jchar*>(str16), - strLen); - } - - // If one of our NewString{UTF} calls failed due to memory, an - // exception will be pending. - if (env->ExceptionCheck()) { - res.unlockBag(startOfBag); - return NULL; - } - - env->SetObjectArrayElement(array, i, str); - - // str is not NULL at that point, otherwise ExceptionCheck would have been true. - // If we have a large amount of strings in our array, we might - // overflow the local reference table of the VM. - env->DeleteLocalRef(str); - } - } - res.unlockBag(startOfBag); - return array; +static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint priority, jstring tag, jstring prefix) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + (void) theme; + (void) priority; + (void) tag; + (void) prefix; } -static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - value = bag->map.value; - - // Take care of resolving the found resource to its final value. - ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - if (value.dataType >= Res_value::TYPE_FIRST_INT - && value.dataType <= Res_value::TYPE_LAST_INT) { - int intVal = value.data; - env->SetIntArrayRegion(array, i, 1, &intVal); - } - } - res.unlockBag(startOfBag); - return array; +static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/, + jlong theme_ptr) { + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + return static_cast<jint>(theme->GetChangingConfigurations()); } -static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz, - jint styleId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(styleId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } +static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + delete reinterpret_cast<Asset*>(asset_ptr); +} - const ResTable::bag_entry* bag = startOfBag; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - int resourceId = bag->map.name.ident; - env->SetIntArrayRegion(array, i, 1, &resourceId); - } - res.unlockBag(startOfBag); - return array; +static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + uint8_t b; + ssize_t res = asset->read(&b, sizeof(b)); + return res == sizeof(b) ? static_cast<jint>(b) : -1; } -static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) -{ - if (isSystem) { - verifySystemIdmaps(); - } - AssetManager* am = new AssetManager(); - if (am == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", ""); - return; - } +static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer, + jint offset, jint len) { + if (len == 0) { + return 0; + } - am->addDefaultAssets(); + jsize buffer_len = env->GetArrayLength(java_buffer); + if (offset < 0 || offset >= buffer_len || len < 0 || len > buffer_len || + offset > buffer_len - len) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", ""); + return -1; + } - ALOGV("Created AssetManager %p for Java object %p\n", am, clazz); - env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am)); -} + ScopedByteArrayRW byte_array(env, java_buffer); + if (byte_array.get() == nullptr) { + return -1; + } -static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz) -{ - AssetManager* am = (AssetManager*) - (env->GetLongField(clazz, gAssetManagerOffsets.mObject)); - ALOGV("Destroying AssetManager %p for Java object %p\n", am, clazz); - if (am != NULL) { - delete am; - env->SetLongField(clazz, gAssetManagerOffsets.mObject, 0); - } + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + ssize_t res = asset->read(byte_array.get() + offset, len); + if (res < 0) { + jniThrowException(env, "java/io/IOException", ""); + return -1; + } + return res > 0 ? static_cast<jint>(res) : -1; } -static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz) -{ - return Asset::getGlobalCount(); +static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset, + jint whence) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->seek( + static_cast<off64_t>(offset), (whence > 0 ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR)))); } -static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, jobject clazz) -{ - String8 alloc = Asset::getAssetAllocations(); - if (alloc.length() <= 0) { - return NULL; - } - - jstring str = env->NewStringUTF(alloc.string()); - return str; +static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->getLength()); } -static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz) -{ - return AssetManager::getGlobalCount(); +static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->getRemainingLength()); } // ---------------------------------------------------------------------------- -/* - * JNI registration. - */ +// JNI registration. static const JNINativeMethod gAssetManagerMethods[] = { - /* name, signature, funcPtr */ - - // Basic asset stuff. - { "openAsset", "(Ljava/lang/String;I)J", - (void*) android_content_AssetManager_openAsset }, - { "openAssetFd", "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*) android_content_AssetManager_openAssetFd }, - { "openNonAssetNative", "(ILjava/lang/String;I)J", - (void*) android_content_AssetManager_openNonAssetNative }, - { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*) android_content_AssetManager_openNonAssetFdNative }, - { "list", "(Ljava/lang/String;)[Ljava/lang/String;", - (void*) android_content_AssetManager_list }, - { "destroyAsset", "(J)V", - (void*) android_content_AssetManager_destroyAsset }, - { "readAssetChar", "(J)I", - (void*) android_content_AssetManager_readAssetChar }, - { "readAsset", "(J[BII)I", - (void*) android_content_AssetManager_readAsset }, - { "seekAsset", "(JJI)J", - (void*) android_content_AssetManager_seekAsset }, - { "getAssetLength", "(J)J", - (void*) android_content_AssetManager_getAssetLength }, - { "getAssetRemainingLength", "(J)J", - (void*) android_content_AssetManager_getAssetRemainingLength }, - { "addAssetPathNative", "(Ljava/lang/String;Z)I", - (void*) android_content_AssetManager_addAssetPath }, - { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I", - (void*) android_content_AssetManager_addAssetFd }, - { "addOverlayPathNative", "(Ljava/lang/String;)I", - (void*) android_content_AssetManager_addOverlayPath }, - { "isUpToDate", "()Z", - (void*) android_content_AssetManager_isUpToDate }, - - // Resources. - { "getLocales", "()[Ljava/lang/String;", - (void*) android_content_AssetManager_getLocales }, - { "getNonSystemLocales", "()[Ljava/lang/String;", - (void*) android_content_AssetManager_getNonSystemLocales }, - { "getSizeConfigurations", "()[Landroid/content/res/Configuration;", - (void*) android_content_AssetManager_getSizeConfigurations }, - { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V", - (void*) android_content_AssetManager_setConfiguration }, - { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*) android_content_AssetManager_getResourceIdentifier }, - { "getResourceName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceName }, - { "getResourcePackageName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourcePackageName }, - { "getResourceTypeName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceTypeName }, - { "getResourceEntryName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceEntryName }, - { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadResourceValue }, - { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadResourceBagValue }, - { "getStringBlockCount","()I", - (void*) android_content_AssetManager_getStringBlockCount }, - { "getNativeStringBlock","(I)J", - (void*) android_content_AssetManager_getNativeStringBlock }, - { "getCookieName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getCookieName }, - { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;", - (void*) android_content_AssetManager_getAssignedPackageIdentifiers }, - - // Themes. - { "newTheme", "()J", - (void*) android_content_AssetManager_newTheme }, - { "deleteTheme", "(J)V", - (void*) android_content_AssetManager_deleteTheme }, - { "applyThemeStyle", "(JIZ)V", - (void*) android_content_AssetManager_applyThemeStyle }, - { "copyTheme", "(JJ)V", - (void*) android_content_AssetManager_copyTheme }, - { "clearTheme", "(J)V", - (void*) android_content_AssetManager_clearTheme }, - { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadThemeAttributeValue }, - { "getThemeChangingConfigurations", "(J)I", - (void*) android_content_AssetManager_getThemeChangingConfigurations }, - { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V", - (void*) android_content_AssetManager_dumpTheme }, - { "applyStyle","(JIIJ[IIJJ)V", - (void*) android_content_AssetManager_applyStyle }, - { "resolveAttrs","(JII[I[I[I[I)Z", - (void*) android_content_AssetManager_resolveAttrs }, - { "retrieveAttributes","(J[I[I[I)Z", - (void*) android_content_AssetManager_retrieveAttributes }, - { "getArraySize","(I)I", - (void*) android_content_AssetManager_getArraySize }, - { "retrieveArray","(I[I)I", - (void*) android_content_AssetManager_retrieveArray }, - - // XML files. - { "openXmlAssetNative", "(ILjava/lang/String;)J", - (void*) android_content_AssetManager_openXmlAssetNative }, - - // Arrays. - { "getArrayStringResource","(I)[Ljava/lang/String;", - (void*) android_content_AssetManager_getArrayStringResource }, - { "getArrayStringInfo","(I)[I", - (void*) android_content_AssetManager_getArrayStringInfo }, - { "getArrayIntResource","(I)[I", - (void*) android_content_AssetManager_getArrayIntResource }, - { "getStyleAttributes","(I)[I", - (void*) android_content_AssetManager_getStyleAttributes }, - - // Bookkeeping. - { "init", "(Z)V", - (void*) android_content_AssetManager_init }, - { "destroy", "()V", - (void*) android_content_AssetManager_destroy }, - { "getGlobalAssetCount", "()I", - (void*) android_content_AssetManager_getGlobalAssetCount }, - { "getAssetAllocations", "()Ljava/lang/String;", - (void*) android_content_AssetManager_getAssetAllocations }, - { "getGlobalAssetManagerCount", "()I", - (void*) android_content_AssetManager_getGlobalAssetManagerCount }, + // AssetManager setup methods. + {"nativeCreate", "()J", (void*)NativeCreate}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, + {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V", + (void*)NativeSetConfiguration}, + {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;", + (void*)NativeGetAssignedPackageIdentifiers}, + + // AssetManager file methods. + {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList}, + {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset}, + {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenAssetFd}, + {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset}, + {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenNonAssetFd}, + {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset}, + + // AssetManager resource methods. + {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue}, + {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I", + (void*)NativeGetResourceBagValue}, + {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes}, + {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;", + (void*)NativeGetResourceStringArray}, + {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo}, + {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray}, + {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize}, + {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray}, + + // AssetManager resource name/ID methods. + {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)NativeGetResourceIdentifier}, + {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName}, + {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName}, + {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName}, + {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName}, + {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales}, + {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;", + (void*)NativeGetSizeConfigurations}, + + // Style attribute related methods. + {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle}, + {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs}, + {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes}, + + // Theme related methods. + {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate}, + {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy}, + {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle}, + {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy}, + {"nativeThemeClear", "(J)V", (void*)NativeThemeClear}, + {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I", + (void*)NativeThemeGetAttributeValue}, + {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump}, + {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations}, + + // AssetInputStream methods. + {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy}, + {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar}, + {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead}, + {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek}, + {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength}, + {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength}, + + // System/idmap related methods. + {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps}, + + // Global management/debug methods. + {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount}, + {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations}, + {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount}, }; -int register_android_content_AssetManager(JNIEnv* env) -{ - jclass typedValue = FindClassOrDie(env, "android/util/TypedValue"); - gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I"); - gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I"); - gTypedValueOffsets.mString = GetFieldIDOrDie(env, typedValue, "string", - "Ljava/lang/CharSequence;"); - gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I"); - gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I"); - gTypedValueOffsets.mChangingConfigurations = GetFieldIDOrDie(env, typedValue, - "changingConfigurations", "I"); - gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); - - jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); - gAssetFileDescriptorOffsets.mFd = GetFieldIDOrDie(env, assetFd, "mFd", - "Landroid/os/ParcelFileDescriptor;"); - gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); - gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); - - jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); - gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); - - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - g_stringClass = MakeGlobalRefOrDie(env, stringClass); - - jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray"); - gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass); - gSparseArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, - "<init>", "()V"); - gSparseArrayOffsets.put = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", - "(ILjava/lang/Object;)V"); - - jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration"); - gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass); - gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, - "<init>", "()V"); - gConfigurationOffsets.mSmallestScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass, - "smallestScreenWidthDp", "I"); - gConfigurationOffsets.mScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass, - "screenWidthDp", "I"); - gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass, - "screenHeightDp", "I"); - - return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods, - NELEM(gAssetManagerMethods)); +int register_android_content_AssetManager(JNIEnv* env) { + jclass apk_assets_class = FindClassOrDie(env, "android/content/res/ApkAssets"); + gApkAssetsFields.native_ptr = GetFieldIDOrDie(env, apk_assets_class, "mNativePtr", "J"); + + jclass typedValue = FindClassOrDie(env, "android/util/TypedValue"); + gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I"); + gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I"); + gTypedValueOffsets.mString = + GetFieldIDOrDie(env, typedValue, "string", "Ljava/lang/CharSequence;"); + gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I"); + gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I"); + gTypedValueOffsets.mChangingConfigurations = + GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I"); + gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); + + jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); + gAssetFileDescriptorOffsets.mFd = + GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;"); + gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); + gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); + + jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); + gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); + + jclass stringClass = FindClassOrDie(env, "java/lang/String"); + g_stringClass = MakeGlobalRefOrDie(env, stringClass); + + jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray"); + gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass); + gSparseArrayOffsets.constructor = + GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "<init>", "()V"); + gSparseArrayOffsets.put = + GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V"); + + jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration"); + gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass); + gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, "<init>", "()V"); + gConfigurationOffsets.mSmallestScreenWidthDpOffset = + GetFieldIDOrDie(env, configurationClass, "smallestScreenWidthDp", "I"); + gConfigurationOffsets.mScreenWidthDpOffset = + GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I"); + gConfigurationOffsets.mScreenHeightDpOffset = + GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I"); + + return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods, + NELEM(gAssetManagerMethods)); } }; // namespace android diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 1350f3f46aca..5b788a644852 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -673,7 +673,7 @@ jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) nativeData->mObject = val; gNativeDataCache = nullptr; ++gNumProxies; - if (++gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) { + if (gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) { ALOGW("Unexpectedly many live BinderProxies: %d\n", gNumProxies); gProxiesWarned = gNumProxies; } diff --git a/core/jni/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h index 8dd933707a6a..2c1e3579eb92 100644 --- a/core/jni/include/android_runtime/android_util_AssetManager.h +++ b/core/jni/include/android_runtime/android_util_AssetManager.h @@ -14,17 +14,20 @@ * limitations under the License. */ -#ifndef android_util_AssetManager_H -#define android_util_AssetManager_H +#ifndef ANDROID_RUNTIME_ASSETMANAGER_H +#define ANDROID_RUNTIME_ASSETMANAGER_H -#include <androidfw/AssetManager.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/MutexGuard.h" #include "jni.h" namespace android { -extern AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject assetMgr); +extern AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager); +extern Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager); +extern Guarded<AssetManager2>* AssetManagerForNdkAssetManager(AAssetManager* assetmanager); -} +} // namespace android -#endif +#endif // ANDROID_RUNTIME_ASSETMANAGER_H diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 0b0ed8386c60..698f394385ab 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -204,7 +204,10 @@ message IncidentProto { (section).args = "power --proto" ]; - optional android.service.print.PrintServiceDumpProto print = 3010; + optional android.service.print.PrintServiceDumpProto print = 3010 [ + (section).type = SECTION_DUMPSYS, + (section).args = "print --proto" + ]; optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [ (section).type = SECTION_DUMPSYS, diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto index 0342c9c89527..d1c5db66a841 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -104,11 +104,6 @@ message AlarmManagerServiceProto { repeated InFlightProto outstanding_deliveries = 34; - // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It - // should be either CosntantsProto.allow_while_idle_short_duration_ms or - // ConstantsProto.allow_while_idle_long_duration_ms. - optional int64 allow_while_idle_min_duration_ms = 35; - message LastAllowWhileIdleDispatch { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -121,7 +116,7 @@ message AlarmManagerServiceProto { } // Whether the short or long while-idle timeout should be used for each UID. - repeated int32 use_allow_while_idle_short_time = 42; + repeated int32 use_allow_while_idle_short_time = 35; // For each uid, this is the last time we dispatched an "allow while idle" // alarm, used to determine the earliest we can dispatch the next such alarm. diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index b5c3ac086512..5cb5319f4fea 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -227,7 +227,6 @@ message PowerServiceSettingsAndConfigurationDumpProto { 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 @@ message PowerServiceSettingsAndConfigurationDumpProto { // 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 449e54672207..c11058a22107 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -295,6 +295,7 @@ message WindowStateProto { optional bool animating_exit = 14; repeated WindowStateProto child_windows = 15; optional .android.graphics.RectProto surface_position = 16; + optional .android.graphics.RectProto shown_position = 17; optional int32 requested_width = 18; optional int32 requested_height = 19; optional int32 view_visibility = 20; diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index ef777de06163..f8050a15e78e 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -49,7 +49,8 @@ message PackageServiceDumpProto { 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 c2be7f175816..f783b866c0ac 100644 --- a/core/proto/android/service/print.proto +++ b/core/proto/android/service/print.proto @@ -21,13 +21,18 @@ option java_multiple_files = true; option java_outer_classname = "PrintServiceProto"; import "frameworks/base/core/proto/android/content/component_name.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message PrintServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Each user has a separate printer state repeated PrintUserStateProto userStates = 1; } message PrintUserStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Should be 0, 10, 11, 12, etc. where 0 is the owner. optional int32 user_id = 1; @@ -51,6 +56,8 @@ message PrintUserStateProto { } message PrintSpoolerStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Is the print spooler destroyed? optional bool is_destroyed = 1; @@ -62,6 +69,8 @@ message PrintSpoolerStateProto { } message PrintSpoolerInternalStateProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Print jobs repeated PrintJobInfoProto print_jobs = 1; @@ -73,6 +82,8 @@ message PrintSpoolerInternalStateProto { } message PrinterCapabilitiesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Minimum margins of the printer optional MarginsProto min_margins = 1; @@ -90,6 +101,8 @@ message PrinterCapabilitiesProto { } message PrinterInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // The id of the printer optional PrinterIdProto id = 1; @@ -120,6 +133,8 @@ message PrinterInfoProto { } message PrinterDiscoverySessionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Is this session destroyed? optional bool is_destroyed = 1; @@ -140,6 +155,8 @@ message PrinterDiscoverySessionProto { } message InstalledPrintServiceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Component name of the service optional android.content.ComponentNameProto component_name = 1; @@ -154,14 +171,18 @@ message InstalledPrintServiceProto { } message PrinterIdProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Component name of the service that reported the printer optional android.content.ComponentNameProto service_name = 1; // Local id of the printer - optional string local_id = 2; + optional string local_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; } message ActivePrintServiceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Component name of the service optional android.content.ComponentNameProto component_name = 1; @@ -185,6 +206,8 @@ message ActivePrintServiceProto { } message MediaSizeProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Id of this media size optional string id = 1; @@ -199,6 +222,8 @@ message MediaSizeProto { } message ResolutionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Id of this resolution optional string id = 1; @@ -213,6 +238,8 @@ message ResolutionProto { } message MarginsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Space at the top optional int32 top_mils = 1; @@ -227,6 +254,8 @@ message MarginsProto { } message PrintAttributesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Media to use optional ResolutionProto media_size = 1; @@ -270,8 +299,10 @@ message PrintAttributesProto { } message PrintDocumentInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Name of the document to print - optional string name = 1; + optional string name = 1 [ (android.privacy).dest = DEST_EXPLICIT ]; // Number of pages in the doc optional int32 page_count = 2; @@ -284,6 +315,8 @@ message PrintDocumentInfoProto { } message PageRangeProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Start of the range optional int32 start = 1; @@ -292,8 +325,10 @@ message PageRangeProto { } message PrintJobInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Label of the job - optional string label = 1; + optional string label = 1 [ (android.privacy).dest = DEST_EXPLICIT ]; // Id of the job optional string print_job_id = 2; @@ -359,6 +394,8 @@ message PrintJobInfoProto { } message CachedPrintJobProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // The id of the app the job belongs to optional int32 app_id = 1; diff --git a/core/proto/android/telecomm/enums.proto b/core/proto/android/telecomm/enums.proto new file mode 100644 index 000000000000..7a2ba624c021 --- /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 3ac8f3b22fd2..cbd06fdb8c04 100644 --- a/core/proto/android/view/displayinfo.proto +++ b/core/proto/android/view/displayinfo.proto @@ -29,4 +29,5 @@ message DisplayInfoProto { 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 deefddbf5f73..f6f1d81ce860 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" /> diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index a6dee8a9beb3..f5bbadcce06b 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2017 The Android Open Source Project +Copyright (C) 2018 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,36 +13,21 @@ Copyright (C) 2017 The Android Open Source Project See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" - android:viewportWidth="48" - android:viewportHeight="48"> - <group> - <path - android:fillColor="#2C292A" - android:fillType="evenOdd" - android:pathData="M6,26a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/> - <path - android:fillColor="#FAFAFA" - android:fillType="evenOdd" - android:pathData="M4,24a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/> - <path - android:fillColor="#2C292A" - android:fillType="evenOdd" - android:pathData="M2,22a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/> - <path - android:fillColor="#00000000" - android:strokeColor="#453F41" - android:strokeWidth="1" - android:fillType="evenOdd" - android:pathData="M26.5 29.5v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3h-1v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3H17a1.5 1.5 0 0 1-1.5-1.5V17.5h13V28a1.5 1.5 0 0 1-1.5 1.5h-.5zM13.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM30.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM26.3 12.11A6.46 6.46 0 0 1 28.5 17v.5h-13V17a6.46 6.46 0 0 1 2.2-4.89l-.9-.9a.98.98 0 0 1 0-1.41.98.98 0 0 1 1.4 0l1.26 1.25A6.33 6.33 0 0 1 22 10.5c.87 0 1.73.2 2.54.55L25.8 9.8a.98.98 0 0 1 1.4 0 .98.98 0 0 1 0 1.4l-.9.91z"/> - <path - android:fillColor="#453F41" - android:fillType="evenOdd" - android:pathData="M20.16 14.5a.66.66 0 1 1-1.31 0c0-.36.29-.65.65-.65.36 0 .65.29.65.65zM25.16 14.5c0 .36-.3.66-.66.66a.65.65 0 1 1 .66-.66z"/> - <path - android:fillColor="#453F41" - android:pathData="M22 40.5c0.36 0 0.73-0.01 1.09-0.03l-0.18-3A15.77 15.77 0 0 1 22 37.5v3zm2.17-0.13a18.48 18.48 0 0 0 1.08-0.15l-0.53-2.96c-0.3 0.05-0.6 0.1-0.9 0.13l0.35 2.98zM26.32 40a18.37 18.37 0 0 0 1.05-0.28l-0.87-2.87a15.37 15.37 0 0 1-0.88 0.23l0.7 2.92zm2.1-0.64l-1.03-2.81a15.39 15.39 0 0 0 0.84-0.34l1.2 2.74a18.39 18.39 0 0 1-1 0.41zm1.99-0.87l-1.37-2.67a15.46 15.46 0 0 0 0.8-0.44l1.52 2.59a18.46 18.46 0 0 1-0.95 0.52zm1.89-1.11l-1.67-2.5a15.55 15.55 0 0 0 0.74-0.52l1.81 2.39a18.55 18.55 0 0 1-0.88 0.63zm1.75-1.33l-1.95-2.28a15.6 15.6 0 0 0 0.67-0.61l2.09 2.15a18.6 18.6 0 0 1-0.8 0.74zm1.6-1.55l-2.22-2.02a15.6 15.6 0 0 0 0.6-0.7l2.33 1.9a18.6 18.6 0 0 1-0.72 0.82zM37 32.82l-2.43-1.76a15.53 15.53 0 0 0 0.5-0.75l2.54 1.6c-0.2 0.31-0.4 0.61-0.61 0.9zm1.15-1.8l-2.62-1.47a15.45 15.45 0 0 0 0.42-0.8l2.7 1.3a18.45 18.45 0 0 1-0.5 0.97zm0.95-1.98l-2.77-1.14a15.38 15.38 0 0 0 0.32-0.86l2.84 0.98a18.38 18.38 0 0 1-0.39 1.02zm0.72-2.09c0.1-0.34 0.18-0.7 0.26-1.05l-2.93-0.63a15.38 15.38 0 0 1-0.22 0.88l2.89 0.8zm0.46-2.15a18.52 18.52 0 0 0 0.13-1.08l-2.99-0.28a15.52 15.52 0 0 1-0.1 0.9l2.96 0.46zm0.2-2.2a18.81 18.81 0 0 0 0-1.1l-3 0.08a16 16 0 0 1 0 0.92l3 0.1zm-0.06-2.2a18.54 18.54 0 0 0-0.12-1.07l-2.97 0.43c0.04 0.3 0.08 0.6 0.1 0.9l3-0.25zm-0.31-2.15a18.39 18.39 0 0 0-0.25-1.06l-2.9 0.78a15.39 15.39 0 0 1 0.21 0.89l2.94-0.6zm-0.57-2.12l-2.85 0.95a15.37 15.37 0 0 0-0.31-0.85l2.78-1.12a18.37 18.37 0 0 1 0.38 1.02zm-0.83-2.06l-2.71 1.29a15.44 15.44 0 0 0-0.42-0.81l2.63-1.45a18.44 18.44 0 0 1 0.5 0.97zm-1.03-1.88l-2.54 1.6a15.53 15.53 0 0 0-0.5-0.76l2.44-1.74 0.6 0.9zm-1.28-1.79l-2.33 1.88a15.6 15.6 0 0 0-0.6-0.69l2.23-2.02a18.6 18.6 0 0 1 0.7 0.83zm-1.48-1.63l-2.1 2.14a15.6 15.6 0 0 0-0.67-0.62l1.97-2.26a18.6 18.6 0 0 1 0.8 0.74zM33.24 7.3l-1.82 2.38a15.55 15.55 0 0 0-0.74-0.53l1.68-2.49c0.3 0.2 0.6 0.42 0.88 0.64zm-1.71-1.17L29.98 8.7a15.47 15.47 0 0 0-0.8-0.45l1.4-2.66a18.47 18.47 0 0 1 0.95 0.54zm-1.95-1.02l-1.23 2.74A15.4 15.4 0 0 0 27.5 7.5l1.06-2.8a18.4 18.4 0 0 1 1.01 0.4zm-2.06-0.78l-0.9 2.86a15.37 15.37 0 0 0-0.87-0.24l0.72-2.92a18.37 18.37 0 0 1 1.05 0.3zM25.38 3.8a18.47 18.47 0 0 0-1.08-0.17l-0.37 2.98c0.3 0.04 0.6 0.08 0.9 0.14l0.55-2.95zm-2.2-0.27A18.75 18.75 0 0 0 22.1 3.5l-0.02 3L23 6.53l0.19-3zM21 3.53a18.6 18.6 0 0 0-1.08 0.09l0.33 2.98a15.6 15.6 0 0 1 0.91-0.08l-0.16-3zm-2.16 0.24A18.4 18.4 0 0 0 17.76 4l0.68 2.92a15.4 15.4 0 0 1 0.9-0.18l-0.51-2.96zm-2.14 0.5l0.86 2.88a15.37 15.37 0 0 0-0.86 0.28l-1.03-2.81a18.37 18.37 0 0 1 1.03-0.35zm-2.07 0.76l1.2 2.75a15.42 15.42 0 0 0-0.83 0.4L13.63 5.5a18.42 18.42 0 0 1 0.99-0.47zM12.7 6l1.5 2.6a15.5 15.5 0 0 0-0.76 0.48l-1.66-2.5A18.5 18.5 0 0 1 12.7 6zm-1.83 1.22l1.8 2.4a15.58 15.58 0 0 0-0.7 0.57L10.01 7.9a18.58 18.58 0 0 1 0.85-0.68zM9.19 8.66l2.07 2.16a15.6 15.6 0 0 0-0.63 0.65l-2.2-2.04a18.6 18.6 0 0 1 0.76-0.77zm-1.51 1.63l2.32 1.9a15.57 15.57 0 0 0-0.56 0.72l-2.42-1.76a18.57 18.57 0 0 1 0.66-0.86zm-1.23 1.69l2.52 1.62a15.5 15.5 0 0 0-0.47 0.78l-2.61-1.47a18.5 18.5 0 0 1 0.56-0.93zm-1.08 1.9l2.7 1.32a15.41 15.41 0 0 0-0.38 0.83l-2.77-1.15a18.41 18.41 0 0 1 0.45-1zm-0.85 2.04l2.84 0.98a15.37 15.37 0 0 0-0.28 0.87L4.2 16.96c0.1-0.35 0.2-0.7 0.32-1.04zm-0.6 2.12a18.43 18.43 0 0 0-0.2 1.07l2.97 0.47c0.05-0.3 0.1-0.6 0.17-0.9l-2.93-0.64zm-0.34 2.18a18.65 18.65 0 0 0-0.07 1.09l3 0.11 0.06-0.91-2.99-0.29zm-0.08 2.2a18.7 18.7 0 0 0 0.06 1.1l3-0.25a15.7 15.7 0 0 1-0.06-0.91l-3 0.07zm0.18 2.18a18.44 18.44 0 0 0 0.18 1.07l2.95-0.6a15.44 15.44 0 0 1-0.16-0.9L3.68 24.6zm0.43 2.14l2.9-0.77a15.37 15.37 0 0 0 0.26 0.88l-2.85 0.94a18.37 18.37 0 0 1-0.3-1.05zm0.7 2.1l2.78-1.11a15.4 15.4 0 0 0 0.36 0.83l-2.71 1.27a18.4 18.4 0 0 1-0.44-1zm0.9 1.95l2.65-1.43a15.48 15.48 0 0 0 0.45 0.8l-2.55 1.57a18.48 18.48 0 0 1-0.54-0.94zm1.17 1.87l2.45-1.73a15.56 15.56 0 0 0 0.54 0.73l-2.34 1.87a18.56 18.56 0 0 1-0.65-0.87zm1.37 1.72l2.23-2a15.6 15.6 0 0 0 0.63 0.65l-2.1 2.14a18.6 18.6 0 0 1-0.76-0.79zm1.58 1.56l1.98-2.26c0.22 0.2 0.46 0.39 0.7 0.58l-1.84 2.37a18.59 18.59 0 0 1-0.84-0.7zm1.66 1.28l1.7-2.46a15.52 15.52 0 0 0 0.77 0.5l-1.56 2.56a18.52 18.52 0 0 1-0.91-0.6zm1.87 1.14l1.4-2.65a15.43 15.43 0 0 0 0.82 0.4l-1.24 2.73a18.43 18.43 0 0 1-0.98-0.48zm2 0.91l1.08-2.8a15.37 15.37 0 0 0 0.86 0.3l-0.9 2.86a18.37 18.37 0 0 1-1.04-0.36zm2.1 0.67a18.4 18.4 0 0 0 1.07 0.23l0.56-2.94a15.4 15.4 0 0 1-0.9-0.2l-0.72 2.91zm2.18 0.41a18.57 18.57 0 0 0 1.08 0.1l0.2-2.99a15.57 15.57 0 0 1-0.9-0.09l-0.38 2.98zm2.2 0.15H22v-3h-0.13l-0.03 3z"/> - </group> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="vector" + android:width="640dp" + android:height="640dp" + android:viewportWidth="64" + android:viewportHeight="64"> + <path + android:name="bg" + android:pathData="M 27 43 L 32 43 C 38.075 43 43 38.075 43 32 C 43 25.925 38.075 21 32 21 C 25.925 21 21 25.925 21 32 L 21 64" + android:strokeColor="#6823a1" + android:strokeWidth="16"/> + <path + android:name="fg" + android:pathData="M 29 43 L 32 43 C 38.075 43 43 38.075 43 32 C 43 25.925 38.075 21 32 21 C 25.925 21 21 25.925 21 32 L 21 64" + android:strokeColor="#ff0000" + android:strokeWidth="8"/> </vector> diff --git a/core/res/res/drawable-nodpi/platlogo_m.xml b/core/res/res/drawable-nodpi/platlogo_m.xml index aacf67483d72..8e43638ef221 100644 --- a/core/res/res/drawable-nodpi/platlogo_m.xml +++ b/core/res/res/drawable-nodpi/platlogo_m.xml @@ -18,23 +18,4 @@ Copyright (C) 2017 The Android Open Source Project android:height="480dp" android:viewportWidth="48.0" android:viewportHeight="48.0"> - <!--<path - android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0" - android:fillAlpha="0.066" - android:fillColor="#000000"/>--> - <path - android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0" - android:fillColor="#FFC107"/> - <path - android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z" - android:fillColor="#FE9F00"/> - <path - android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0" - android:fillColor="#FED44F"/> - <path - android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z" - android:fillColor="#FFC107"/> - <path - android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0" - android:fillColor="#FFFFFF"/> </vector> diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml index 2e2b3956dc68..0fde2cc2aba4 100644 --- a/core/res/res/drawable-nodpi/stat_sys_adb.xml +++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2017 The Android Open Source Project +Copyright (C) 2018 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,24 +13,24 @@ Copyright (C) 2017 The Android Open Source Project See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <group> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="vector" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + <group android:name="stat_sys_adb"> <path - android:fillColor="#FFFFFF" - android:fillAlpha=".33" - android:fillType="evenOdd" - android:pathData="M5.71 18.29A8.99 8.99 0 0 0 22 13c0-3-1.46-5.65-3.71-7.29A8.99 8.99 0 0 0 2 11c0 3 1.46 5.65 3.71 7.29z"/> + android:name="outer" + android:pathData="M 18 30 L 24 30 C 29.523 30 34 25.523 34 20 C 34 14.477 29.523 10 24 10 C 18.477 10 14 14.477 14 20 L 14 44" + android:strokeColor="#000000" + android:strokeWidth="10"/> <path - android:fillColor="#FFFFFF" - android:fillType="evenOdd" - android:pathData="M7.25 19.18A8.5 8.5 0 0 0 19.19 7.24 9 9 0 0 1 7.24 19.19z"/> - <path - android:fillColor="#FFFFFF" - android:fillAlpha=".33" - android:pathData="M10.5 3a0.5 0.5 0 1 1 1 0v2.05a0.5 0.5 0 1 1-1 0V3zm3.1 0.42a0.5 0.5 0 0 1 0.93 0.39l-0.8 1.88A0.5 0.5 0 1 1 12.8 5.3l0.8-1.88zm2.7 1.57a0.5 0.5 0 1 1 0.71 0.7l-1.45 1.46a0.5 0.5 0 0 1-0.7-0.71l1.44-1.45zm1.9 2.5a0.5 0.5 0 0 1 0.38 0.92l-1.9 0.77a0.5 0.5 0 0 1-0.37-0.93l1.9-0.77zM19 10.5a0.5 0.5 0 1 1 0 1h-2.05a0.5 0.5 0 0 1 0-1H19zm-0.42 3.1a0.5 0.5 0 0 1-0.39 0.93l-1.88-0.8a0.5 0.5 0 1 1 0.39-0.92l1.88 0.8zm-1.57 2.7a0.5 0.5 0 1 1-0.7 0.71l-1.46-1.45a0.5 0.5 0 0 1 0.71-0.7l1.45 1.44zm-2.5 1.9a0.5 0.5 0 1 1-0.92 0.38l-0.77-1.9a0.5 0.5 0 0 1 0.93-0.37l0.77 1.9zM11.5 19a0.5 0.5 0 1 1-1 0v-2.05a0.5 0.5 0 0 1 1 0V19zm-3.1-0.42a0.5 0.5 0 0 1-0.93-0.39l0.8-1.88A0.5 0.5 0 0 1 9.2 16.7l-0.8 1.88zm-2.7-1.57a0.5 0.5 0 1 1-0.71-0.7l1.45-1.46a0.5 0.5 0 0 1 0.7 0.71L5.7 17.01zm-1.9-2.48a0.5 0.5 0 0 1-0.38-0.92l1.88-0.8a0.5 0.5 0 0 1 0.4 0.92l-1.9 0.8zM3 11.5a0.5 0.5 0 1 1 0-1h2.05a0.5 0.5 0 1 1 0 1H3zm0.42-3.1A0.5 0.5 0 0 1 3.8 7.46l1.88 0.8A0.5 0.5 0 1 1 5.3 9.2L3.42 8.4zm1.57-2.7a0.5 0.5 0 1 1 0.7-0.71l1.46 1.45a0.5 0.5 0 0 1-0.71 0.7L4.99 5.7zm2.5-1.9A0.5 0.5 0 0 1 8.4 3.41l0.77 1.9a0.5 0.5 0 0 1-0.93 0.37L7.48 3.8z"/> + android:name="inner" + android:pathData="M 19 30 L 24 30 C 29.523 30 34 25.523 34 20 C 34 14.477 29.523 10 24 10 C 18.477 10 14 14.477 14 20 L 14 44" + android:strokeColor="#000000" + android:strokeAlpha="0" + android:strokeWidth="6"/> </group> -</vector>
\ No newline at end of file +</vector> diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 4ac308a60d93..bd1030ee14f5 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -28,9 +28,9 @@ android:scaleType="centerCrop" android:importantForAccessibility="no" /> <com.android.internal.widget.RemeasuringLinearLayout - android:id="@+id/message_group_and_sender_container" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_weight="1" android:orientation="vertical"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/message_name" @@ -44,4 +44,10 @@ android:spacing="2dp" android:layout_weight="1"/> </com.android.internal.widget.RemeasuringLinearLayout> + <FrameLayout + android:id="@+id/messaging_group_icon_container" + android:layout_width="@dimen/messaging_avatar_size" + android:layout_height="@dimen/messaging_avatar_size" + android:layout_marginStart="12dp" + android:visibility="gone"/> </com.android.internal.widget.MessagingGroup> diff --git a/core/res/res/layout/notification_template_messaging_image_message.xml b/core/res/res/layout/notification_template_messaging_image_message.xml new file mode 100644 index 000000000000..6ca4dcdd7c9b --- /dev/null +++ b/core/res/res/layout/notification_template_messaging_image_message.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.internal.widget.MessagingImageMessage + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/message_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/messaging_image_extra_spacing" + android:scaleType="fitStart" +/> diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_text_message.xml index ab6466cc7b3c..e728e6912dd4 100644 --- a/core/res/res/layout/notification_template_messaging_message.xml +++ b/core/res/res/layout/notification_template_messaging_text_message.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.internal.widget.MessagingMessage +<com.android.internal.widget.MessagingTextMessage xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/message_text" style="@style/Widget.Material.Notification.MessagingText" diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index ce4ac613824a..e80f16c466b3 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 c8032a276659..9dffe6906a04 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2486,9 +2486,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 e610efd4ea6a..0411c6ed8833 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -262,6 +262,18 @@ <!-- The spacing between messages in Notification.MessagingStyle --> <dimen name="notification_messaging_spacing">6dp</dimen> + <!-- The rounding for messaging images --> + <dimen name="messaging_image_rounding">4dp</dimen> + + <!-- The minimum size for any image in messaging style in order to be displayed --> + <dimen name="messaging_image_min_size">44dp</dimen> + + <!-- The maximum size for any image in messaging style in order to be displayed --> + <dimen name="messaging_image_max_height">136dp</dimen> + + <!-- Extra spacing before and after images in messaging style --> + <dimen name="messaging_image_extra_spacing">8dp</dimen> + <!-- Preferred width and height of the search view. --> <dimen name="search_view_preferred_width">320dip</dimen> <dimen name="search_view_preferred_height">48dip</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index ec81df7b89e2..2b7b056cdf4b 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 29b09b574318..f7b6f06a6806 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3168,7 +3168,8 @@ <java-symbol type="dimen" name="chooser_service_spacing" /> <java-symbol type="bool" name="config_showSysuiShutdown" /> - <java-symbol type="layout" name="notification_template_messaging_message" /> + <java-symbol type="layout" name="notification_template_messaging_text_message" /> + <java-symbol type="layout" name="notification_template_messaging_image_message" /> <java-symbol type="layout" name="notification_template_messaging_group" /> <java-symbol type="id" name="message_text" /> <java-symbol type="id" name="message_name" /> @@ -3183,6 +3184,11 @@ <java-symbol type="id" name="clip_children_tag" /> <java-symbol type="drawable" name="ic_reply_notification_large" /> <java-symbol type="dimen" name="messaging_avatar_size" /> + <java-symbol type="dimen" name="messaging_image_rounding" /> + <java-symbol type="dimen" name="messaging_image_min_size" /> + <java-symbol type="dimen" name="messaging_image_max_height" /> + <java-symbol type="dimen" name="messaging_image_extra_spacing" /> + <java-symbol type="id" name="messaging_group_icon_container" /> <java-symbol type="integer" name="config_stableDeviceDisplayWidth" /> <java-symbol type="integer" name="config_stableDeviceDisplayHeight" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 7d5c60aa292b..53c22f624e8c 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 dca16564aa11..d5be87dbae6b 100644 --- a/core/tests/coretests/res/layout/activity_text_view.xml +++ b/core/tests/coretests/res/layout/activity_text_view.xml @@ -24,6 +24,9 @@ android:id="@+id/textview" android:layout_width="match_parent" android:layout_height="wrap_content" /> + <Space + android:layout_width="1dp" + android:layout_height="60dp"/> <TextView android:id="@+id/nonselectable_textview" diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index aefc47e95512..fb0f5344f643 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 @@ public class ObjectPoolTests { } @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 e92351609256..d125fe790eba 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 static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 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 @@ class TestUtils { 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 77aaa2d7f9ea..0906435db912 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.IApplicationThread; 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.filters.SmallTest; 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 @@ public class TransactionParcelTests { } @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 @@ public class TransactionParcelTests { } @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/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index da86c9f424ad..853a36d29745 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -17,9 +17,11 @@ package android.provider; import static com.google.android.collect.Sets.newHashSet; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; + import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; @@ -194,6 +196,7 @@ public class SettingsBackupTest { Settings.Global.DEVICE_PROVISIONED, Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD, + Settings.Global.DISPLAY_PANEL_LPM, Settings.Global.DISPLAY_SCALING_FORCE, Settings.Global.DISPLAY_SIZE_FORCED, Settings.Global.DNS_RESOLVER_MAX_SAMPLES, @@ -386,7 +389,6 @@ public class SettingsBackupTest { Settings.Global.TZINFO_UPDATE_METADATA_URL, Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, - Settings.Global.UID_CPUPOWER, Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, diff --git a/core/tests/coretests/src/android/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS index 0f85e1f9c5d9..9f2182eca908 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 59d4e55d8d45..657a7fc7b798 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 @@ public class ContextMenuTest extends ActivityInstrumentationTestCase<ContextMenu 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 69e5670f02d5..7f4f9f7b928a 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -66,6 +66,7 @@ import android.support.test.espresso.action.EspressoKey; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; import android.test.suitebuilder.annotation.Suppress; import android.text.InputType; import android.text.Selection; @@ -311,15 +312,69 @@ public class TextViewActivityTest { @Test public void testToolbarAppearsAfterLinkClicked() throws Throwable { - runToolbarAppearsAfterLinkClickedTest(R.id.textview); + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview); + int position = (textLink.getStart() + textLink.getEnd()) / 2; + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + } @Test public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable { - runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview); + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + int position = (textLink.getStart() + textLink.getEnd()) / 2; + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + } + + @Test + public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable { + // Add a link to both selectable and nonselectable TextViews: + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview); + int selectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; + textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; + TextView selectableTextView = mActivity.findViewById(R.id.textview); + TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview); + + onView(withId(R.id.nonselectable_textview)) + .perform(clickOnTextAtIndex(nonselectablePosition)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + assertTrue(nonselectableTextView.hasSelection()); + + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(selectablePosition)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + assertTrue(selectableTextView.hasSelection()); + assertFalse(nonselectableTextView.hasSelection()); + } + + @Test + public void testSelectionRemovedFromNonselectableTextWhenWindowLosesFocus() throws Throwable { + TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; + TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview); + + onView(withId(R.id.nonselectable_textview)) + .perform(clickOnTextAtIndex(nonselectablePosition)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + assertTrue(nonselectableTextView.hasSelection()); + + UiDevice device = UiDevice.getInstance(mInstrumentation); + device.openNotification(); + Thread.sleep(2000); + device.pressBack(); + Thread.sleep(2000); + + assertFalse(nonselectableTextView.hasSelection()); } - private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable { + private TextLinks.TextLink addLinkifiedTextToTextView(int id) throws Throwable { TextView textView = mActivity.findViewById(id); useSystemDefaultTextClassifier(); TextClassificationManager textClassificationManager = @@ -338,11 +393,7 @@ public class TextViewActivityTest { // Wait for the UI thread to refresh Thread.sleep(1000); - TextLinks.TextLink textLink = links.getLinks().iterator().next(); - int position = (textLink.getStart() + textLink.getEnd()) / 2; - onView(withId(id)).perform(clickOnTextAtIndex(position)); - sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); + return links.getLinks().iterator().next(); } @Test diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index 660c744f050d..7b239f05c0d3 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.Handler; 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 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { 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 3919fdd7082f..41082b7c7593 100644 --- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java @@ -127,7 +127,7 @@ public class MessagingLinearLayoutTest { assertEquals(355, mView.getMeasuredHeight());; } - private class FakeImageFloatingTextView extends MessagingMessage { + private class FakeImageFloatingTextView extends MessagingTextMessage { public static final int LINE_HEIGHT = 50; private final int mNumLines; diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index bb9b89be9edb..3495b84e0ab9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -158,7 +158,6 @@ applications that come with the platform <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> - <permission name="android.permission.PACKAGE_USAGE_STATS"/> <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm index 1ef74ba5e1a2..d0565ca7e876 100644 --- a/data/keyboards/Generic.kcm +++ b/data/keyboards/Generic.kcm @@ -472,11 +472,15 @@ key PLUS { ### 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 c4647e04f3c3..c763cc094662 100644 --- a/data/keyboards/Virtual.kcm +++ b/data/keyboards/Virtual.kcm @@ -469,11 +469,15 @@ key PLUS { ### 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 f5bf754ae6ef..7ea35e73619a 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -18,6 +18,8 @@ package android.graphics; 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 @@ public class BitmapFactory { * 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 @@ public class BitmapFactory { * <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 @@ public class BitmapFactory { * 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 f5e863305766..d925441e3657 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1219,10 +1219,14 @@ public class Canvas extends BaseCanvas { 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/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 36a4d26d62bb..8af2fd8bbb5e 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1171,9 +1171,13 @@ public abstract class Drawable { /** * Create a drawable from an inputstream, using the given resources and * value to determine density information. + * + * @deprecated Prefer the version without an Options object. */ - public static Drawable createFromResourceStream(Resources res, TypedValue value, - InputStream is, String srcName, BitmapFactory.Options opts) { + @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 @@ public abstract class Drawable { // 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 f5a6f4910b1b..8b5114c50581 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -825,6 +825,14 @@ public class GradientDrawable extends Drawable { 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 4129868079c7..a8dc34af292b 100644 --- a/graphics/java/android/graphics/drawable/RippleForeground.java +++ b/graphics/java/android/graphics/drawable/RippleForeground.java @@ -110,6 +110,7 @@ class RippleForeground extends RippleComponent { // 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 @@ class RippleForeground extends RippleComponent { 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 251b2e773cfb..70d52164ff74 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -145,6 +145,7 @@ cc_test { "tests/TypeWrappers_test.cpp", "tests/ZipUtils_test.cpp", ], + static_libs: ["libgmock"], target: { android: { srcs: [ @@ -171,6 +172,7 @@ cc_benchmark { // Actual benchmarks. "tests/AssetManager2_bench.cpp", + "tests/AttributeResolution_bench.cpp", "tests/SparseEntry_bench.cpp", "tests/Theme_bench.cpp", ], diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index da0205d72125..60f8a1833919 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -231,12 +231,16 @@ bool ApkAssets::ForEachFile(const std::string& root_path, 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 415d3e36adf9..a8c916bbaea6 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -36,6 +36,31 @@ namespace android { +struct FindEntryResult { + // A pointer to the resource table entry for this resource. + // If the size of the entry is > sizeof(ResTable_entry), it can be cast to + // a ResTable_map_entry and processed as a bag/map. + const ResTable_entry* entry; + + // The configuration for which the resulting entry was defined. This is already swapped to host + // endianness. + ResTable_config config; + + // The bitmask of configuration axis with which the resource value varies. + uint32_t type_flags; + + // The dynamic package ID map for the package from which this resource came from. + const DynamicRefTable* dynamic_ref_table; + + // The string pool reference to the type's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef type_string_ref; + + // The string pool reference to the entry's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef entry_string_ref; +}; + AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); } @@ -44,6 +69,7 @@ bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets bool invalidate_caches) { apk_assets_ = apk_assets; BuildDynamicRefTable(); + RebuildFilterList(); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } @@ -74,12 +100,14 @@ void AssetManager2::BuildDynamicRefTable() { if (idx == 0xff) { package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size()); package_groups_.push_back({}); - package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id; + DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table; + ref_table.mAssignedPackageId = package_id; + ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f; } PackageGroup* package_group = &package_groups_[idx]; // Add the package and to the set of packages with the same ID. - package_group->packages_.push_back(package.get()); + package_group->packages_.push_back(ConfiguredPackage{package.get(), {}}); package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i)); // Add the package name -> build time ID mappings. @@ -94,7 +122,7 @@ void AssetManager2::BuildDynamicRefTable() { // Now assign the runtime IDs so that we have a build-time to runtime ID map. const auto package_groups_end = package_groups_.end(); for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { - const std::string& package_name = iter->packages_[0]->GetPackageName(); + const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName(); for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()), iter->dynamic_ref_table.mAssignedPackageId); @@ -105,20 +133,33 @@ void AssetManager2::BuildDynamicRefTable() { void AssetManager2::DumpToLog() const { base::ScopedLogSeverity _log(base::INFO); + LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this); + std::string list; + for (const auto& apk_assets : apk_assets_) { + base::StringAppendF(&list, "%s,", apk_assets->GetPath().c_str()); + } + LOG(INFO) << "ApkAssets: " << list; + + list = ""; for (size_t i = 0; i < package_ids_.size(); i++) { if (package_ids_[i] != 0xff) { - base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]); + base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]); } } LOG(INFO) << "Package ID map: " << list; for (const auto& package_group: package_groups_) { - list = ""; - for (const auto& package : package_group.packages_) { - base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId()); - } - LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list; + list = ""; + for (const auto& package : package_group.packages_) { + const LoadedPackage* loaded_package = package.loaded_package_; + base::StringAppendF(&list, "%s(%02x%s), ", loaded_package->GetPackageName().c_str(), + loaded_package->GetPackageId(), + (loaded_package->IsDynamic() ? " dynamic" : "")); + } + LOG(INFO) << base::StringPrintf("PG (%02x): ", + package_group.dynamic_ref_table.mAssignedPackageId) + << list; } } @@ -157,52 +198,54 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) { configuration_ = configuration; if (diff) { + RebuildFilterList(); InvalidateCaches(static_cast<uint32_t>(diff)); } } std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system, - bool exclude_mipmap) { + bool exclude_mipmap) const { ATRACE_CALL(); std::set<ResTable_config> configurations; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectConfigurations(exclude_mipmap, &configurations); + package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations); } } return configurations; } std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, - bool merge_equivalent_languages) { + bool merge_equivalent_languages) const { ATRACE_CALL(); std::set<std::string> locales; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectLocales(merge_equivalent_languages, &locales); + package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales); } } return locales; } -std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) { +std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, mode); } std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode) { + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, cookie, mode); } -std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { +std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const { ATRACE_CALL(); std::string full_path = "assets/" + dirname; @@ -236,7 +279,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { // is inconsistent for split APKs. std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie) { + ApkAssetsCookie* out_cookie) const { ATRACE_CALL(); for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) { std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode); @@ -255,7 +298,8 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, - ApkAssetsCookie cookie, Asset::AccessMode mode) { + ApkAssetsCookie cookie, + Asset::AccessMode mode) const { ATRACE_CALL(); if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) { return {}; @@ -264,14 +308,13 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, - bool stop_at_first_match, FindEntryResult* out_entry) { - ATRACE_CALL(); - + bool /*stop_at_first_match*/, + FindEntryResult* out_entry) const { // Might use this if density_override != 0. ResTable_config density_override_config; // Select our configuration or generate a density override configuration. - ResTable_config* desired_config = &configuration_; + const ResTable_config* desired_config = &configuration_; if (density_override != 0 && density_override != configuration_.density) { density_override_config = configuration_; density_override_config.density = density_override; @@ -285,53 +328,135 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri const uint32_t package_id = get_package_id(resid); const uint8_t type_idx = get_type_id(resid) - 1; - const uint16_t entry_id = get_entry_id(resid); + const uint16_t entry_idx = get_entry_id(resid); - const uint8_t idx = package_ids_[package_id]; - if (idx == 0xff) { + const uint8_t package_idx = package_ids_[package_id]; + if (package_idx == 0xff) { LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid); return kInvalidCookie; } - FindEntryResult best_entry; - ApkAssetsCookie best_cookie = kInvalidCookie; - uint32_t cumulated_flags = 0u; - - const PackageGroup& package_group = package_groups_[idx]; + const PackageGroup& package_group = package_groups_[package_idx]; const size_t package_count = package_group.packages_.size(); - FindEntryResult current_entry; - for (size_t i = 0; i < package_count; i++) { - const LoadedPackage* loaded_package = package_group.packages_[i]; - if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) { + + ApkAssetsCookie best_cookie = kInvalidCookie; + const LoadedPackage* best_package = nullptr; + const ResTable_type* best_type = nullptr; + const ResTable_config* best_config = nullptr; + ResTable_config best_config_copy; + uint32_t best_offset = 0u; + uint32_t type_flags = 0u; + + // If desired_config is the same as the set configuration, then we can use our filtered list + // and we don't need to match the configurations, since they already matched. + const bool use_fast_path = desired_config == &configuration_; + + for (size_t pi = 0; pi < package_count; pi++) { + const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; + const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_; + ApkAssetsCookie cookie = package_group.cookies_[pi]; + + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx); + if (UNLIKELY(type_spec == nullptr)) { continue; } - cumulated_flags |= current_entry.type_flags; + uint16_t local_entry_idx = entry_idx; - const ResTable_config* current_config = current_entry.config; - const ResTable_config* best_config = best_entry.config; - if (best_cookie == kInvalidCookie || - current_config->isBetterThan(*best_config, desired_config) || - (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) { - best_entry = current_entry; - best_cookie = package_group.cookies_[i]; - if (stop_at_first_match) { - break; + // If there is an IDMAP supplied with this package, translate the entry ID. + if (type_spec->idmap_entries != nullptr) { + if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) { + // There is no mapping, so the resource is not meant to be in this overlay package. + continue; + } + } + + type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx); + + // If the package is an overlay, then even configurations that are the same MUST be chosen. + const bool package_is_overlay = loaded_package->IsOverlay(); + + const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; + if (use_fast_path) { + const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations; + const size_t type_count = candidate_configs.size(); + for (uint32_t i = 0; i < type_count; i++) { + const ResTable_config& this_config = candidate_configs[i]; + + // We can skip calling ResTable_config::match() because we know that all candidate + // configurations that do NOT match have been filtered-out. + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const ResTable_type* type_chunk = filtered_group.types[i]; + const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = type_chunk; + best_config = &this_config; + best_offset = offset; + } + } + } else { + // This is the slower path, which doesn't use the filtered list of configurations. + // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness + // and fill in any new fields that did not exist when the APK was compiled. + // Furthermore when selecting configurations we can't just record the pointer to the + // ResTable_config, we must copy it. + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + + if (this_config.match(*desired_config)) { + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = *iter; + best_config_copy = this_config; + best_config = &best_config_copy; + best_offset = offset; + } + } } } } - if (best_cookie == kInvalidCookie) { + if (UNLIKELY(best_cookie == kInvalidCookie)) { + return kInvalidCookie; + } + + const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset); + if (UNLIKELY(best_entry == nullptr)) { return kInvalidCookie; } - *out_entry = best_entry; + out_entry->entry = best_entry; + out_entry->config = *best_config; + out_entry->type_flags = type_flags; + out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1); + out_entry->entry_string_ref = + StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index); out_entry->dynamic_ref_table = &package_group.dynamic_ref_table; - out_entry->type_flags = cumulated_flags; return best_cookie; } -bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { +bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const { ATRACE_CALL(); FindEntryResult entry; @@ -341,7 +466,8 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return false; } - const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid); + const LoadedPackage* package = + apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); if (package == nullptr) { return false; } @@ -369,7 +495,7 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return true; } -bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { +bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const { FindEntryResult entry; ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); @@ -383,7 +509,7 @@ bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags) { + uint32_t* out_flags) const { ATRACE_CALL(); FindEntryResult entry; @@ -402,7 +528,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Create a reference since we can't represent this complex type as a Res_value. out_value->dataType = Res_value::TYPE_REFERENCE; out_value->data = resid; - *out_selected_config = *entry.config; + *out_selected_config = entry.config; *out_flags = entry.type_flags; return cookie; } @@ -414,7 +540,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Convert the package ID to the runtime assigned package ID. entry.dynamic_ref_table->lookupResourceValue(out_value); - *out_selected_config = *entry.config; + *out_selected_config = entry.config; *out_flags = entry.type_flags; return cookie; } @@ -422,16 +548,14 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference) { + uint32_t* out_last_reference) const { ATRACE_CALL(); constexpr const int kMaxIterations = 20; for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE && in_out_value->data != 0u && iteration < kMaxIterations; iteration++) { - if (out_last_reference != nullptr) { - *out_last_reference = in_out_value->data; - } + *out_last_reference = in_out_value->data; uint32_t new_flags = 0u; cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/, in_out_value, in_out_selected_config, &new_flags); @@ -492,7 +616,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Attributes, arrays, etc don't have a resource id as the name. They specify // other data, which would be wrong to change via a lookup. if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } @@ -524,7 +649,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { const ResolvedBag* parent_bag = GetBag(parent_resid); if (parent_bag == nullptr) { // Failed to get the parent that should exist. - LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid); + LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, + resid); return nullptr; } @@ -543,7 +669,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t child_key = dtohl(map_entry->name.ident); if (!is_internal_resid(child_key)) { if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, + resid); return nullptr; } } @@ -582,7 +709,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t new_key = dtohl(map_entry->name.ident); if (!is_internal_resid(new_key)) { if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } @@ -638,7 +766,7 @@ static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) { uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const std::string& fallback_type, - const std::string& fallback_package) { + const std::string& fallback_package) const { StringPiece package_name, type, entry; if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) { return 0u; @@ -670,7 +798,8 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const static std::u16string kAttrPrivate16 = u"^attr-private"; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { + for (const ConfiguredPackage& package_impl : package_group.packages_) { + const LoadedPackage* package = package_impl.loaded_package_; if (package_name != package->GetPackageName()) { // All packages in the same group are expected to have the same package name. break; @@ -692,6 +821,32 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, return 0u; } +void AssetManager2::RebuildFilterList() { + for (PackageGroup& group : package_groups_) { + for (ConfiguredPackage& impl : group.packages_) { + // Destroy it. + impl.filtered_configs_.~ByteBucketArray(); + + // Re-create it. + new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); + + // Create the filters here. + impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) { + FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index); + const auto iter_end = spec->types + spec->type_count; + for (auto iter = spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + if (this_config.match(configuration_)) { + group.configurations.push_back(this_config); + group.types.push_back(*iter); + } + } + }); + } + } +} + void AssetManager2::InvalidateCaches(uint32_t diff) { if (diff == 0xffffffffu) { // Everything must go. @@ -872,7 +1027,7 @@ ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value, ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_type_spec_flags, - uint32_t* out_last_ref) { + uint32_t* out_last_ref) const { if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) { uint32_t new_flags; cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags); diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 60e3845d98a9..f912af4f7190 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -20,13 +20,18 @@ #include <log/log.h> +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeFinder.h" -#include "androidfw/ResourceTypes.h" constexpr bool kDebugStyles = false; namespace android { +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1); +} + class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> { public: @@ -44,58 +49,53 @@ class XmlAttributeFinder }; class BagAttributeFinder - : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> { + : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> { public: - BagAttributeFinder(const ResTable::bag_entry* start, - const ResTable::bag_entry* end) - : BackTrackingAttributeFinder(start, end) {} + BagAttributeFinder(const ResolvedBag* bag) + : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr, + bag != nullptr ? bag->entries + bag->entry_count : nullptr) { + } - inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const { - return entry->map.name.ident; + inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const { + return entry->key; } }; -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, - uint32_t def_style_res, uint32_t* src_values, - size_t src_values_length, uint32_t* attrs, - size_t attrs_length, uint32_t* out_values, - uint32_t* out_indices) { +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, + uint32_t* src_values, size_t src_values_length, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, def_style_attr, def_style_res); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { def_style_res = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_end = - def_style_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_res != 0) { + default_style_bag = assetmanager->GetBag(def_style_res); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -106,7 +106,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = -1; + ApkAssetsCookie cookie = kInvalidCookie; uint32_t type_set_flags = 0; value.dataType = Res_value::TYPE_NULL; @@ -122,15 +122,14 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, value.dataType = Res_value::TYPE_ATTRIBUTE; value.data = src_values[ii]; if (kDebugStyles) { - ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, - value.data); + ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data); } } else { - const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_entry != def_style_end) { - block = def_style_entry->stringBlock; - type_set_flags = def_style_type_set_flags; - value = def_style_entry->map.value; + const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -140,22 +139,26 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t resid = 0; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); } } else if (value.data != Res_value::DATA_NULL_EMPTY) { - // If we still don't have a value for this attribute, try to find - // it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + // If we still don't have a value for this attribute, try to find it in the theme! + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -169,7 +172,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = -1; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -179,9 +182,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != -1 ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -195,90 +196,80 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } return true; } -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { - ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", - theme, def_style_attr, def_style_res, xml_parser); + ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme, + def_style_attr, def_style_resid, xml_parser); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, - &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { - def_style_res = value.data; + def_style_resid = value.data; } } } - // Retrieve the style class associated with the current XML tag. - int style = 0; - uint32_t style_bag_type_set_flags = 0; + // Retrieve the style resource ID associated with the current XML tag's style attribute. + uint32_t style_resid = 0u; + uint32_t style_flags = 0u; if (xml_parser != nullptr) { ssize_t idx = xml_parser->indexOfStyle(); if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) { if (value.dataType == value.TYPE_ATTRIBUTE) { - if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) { + // Resolve the attribute with out theme. + if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) { value.dataType = Res_value::TYPE_NULL; } } + if (value.dataType == value.TYPE_REFERENCE) { - style = value.data; + style_resid = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_attr_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_attr_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_attr_end = - def_style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_attr_start, - def_style_attr_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_resid != 0) { + default_style_bag = assetmanager->GetBag(def_style_resid); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Retrieve the style class bag, if requested. - const ResTable::bag_entry* style_attr_start = nullptr; - uint32_t style_type_set_flags = 0; - bag_off = - style != 0 - ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags) - : -1; - style_type_set_flags |= style_bag_type_set_flags; - const ResTable::bag_entry* const style_attr_end = - style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end); + const ResolvedBag* xml_style_bag = nullptr; + if (style_resid != 0) { + xml_style_bag = assetmanager->GetBag(style_resid); + if (xml_style_bag != nullptr) { + style_flags |= xml_style_bag->type_spec_flags; + } + } + + BagAttributeFinder xml_style_attr_finder(xml_style_bag); // Retrieve the XML attributes, if requested. - static const ssize_t kXmlBlock = 0x10000000; XmlAttributeFinder xml_attr_finder(xml_parser); - const size_t xml_attr_end = - xml_parser != nullptr ? xml_parser->getAttributeCount() : 0; // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -289,8 +280,8 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -302,7 +293,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Walk through the xml attributes looking for the requested attribute. const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident); - if (xml_attr_idx != xml_attr_end) { + if (xml_attr_idx != xml_attr_finder.end()) { // We found the attribute we were looking for. xml_parser->getAttributeValue(xml_attr_idx, &value); if (kDebugStyles) { @@ -312,12 +303,12 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the style class values looking for the requested attribute. - const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident); - if (style_attr_entry != style_attr_end) { + const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident); + if (entry != xml_style_attr_finder.end()) { // We found the attribute we were looking for. - block = style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -326,25 +317,25 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the default style values looking for the requested attribute. - const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_attr_entry != def_style_attr_end) { + const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { // We found the attribute we were looking for. - block = def_style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = def_style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } } } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -352,14 +343,15 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -375,7 +367,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -385,9 +377,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -402,36 +392,28 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - // out_indices must NOT be nullptr. out_indices[0] = indices_idx; } -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, - uint32_t* attrs, size_t attrs_length, - uint32_t* out_values, uint32_t* out_indices) { +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { ResTable_config config; Res_value value; int indices_idx = 0; - // Now lock down the resource object and start pulling stuff from it. - res->lock(); - // Retrieve the XML attributes, if requested. const size_t xml_attr_count = xml_parser->getAttributeCount(); size_t ix = 0; uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix); - static const ssize_t kXmlBlock = 0x10000000; - // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -450,28 +432,27 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, cur_xml_attr = xml_parser->getAttributeNameResID(ix); } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - // printf("Resolving attribute reference\n"); - ssize_t new_block = res->resolveReference(&value, block, &resid, - &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } } // Deal with the special @null value -- it turns back to TYPE_NULL. if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -485,8 +466,6 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, out_values += STYLE_NUM_ENTRIES; } - res->unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 28548e27baf0..a65d49b7e32f 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -44,44 +44,6 @@ namespace android { constexpr const static int kAppPackageId = 0x7f; -// Element of a TypeSpec array. See TypeSpec. -struct Type { - // The configuration for which this type defines entries. - // This is already converted to host endianness. - ResTable_config configuration; - - // Pointer to the mmapped data where entry definitions are kept. - const ResTable_type* type; -}; - -// TypeSpec is going to be immediately proceeded by -// an array of Type structs, all in the same block of memory. -struct TypeSpec { - // Pointer to the mmapped data where flags are kept. - // Flags denote whether the resource entry is public - // and under which configurations it varies. - const ResTable_typeSpec* type_spec; - - // Pointer to the mmapped data where the IDMAP mappings for this type - // exist. May be nullptr if no IDMAP exists. - const IdmapEntry_header* idmap_entries; - - // The number of types that follow this struct. - // There is a type for each configuration - // that entries are defined for. - size_t type_count; - - // Trick to easily access a variable number of Type structs - // proceeding this struct, and to ensure their alignment. - const Type types[0]; -}; - -// TypeSpecPtr points to the block of memory that holds -// a TypeSpec struct, followed by an array of Type structs. -// TypeSpecPtr is a managed pointer that knows how to delete -// itself. -using TypeSpecPtr = util::unique_cptr<TypeSpec>; - namespace { // Builder that helps accumulate Type structs and then create a single @@ -95,21 +57,22 @@ class TypeSpecPtrBuilder { } void AddType(const ResTable_type* type) { - ResTable_config config; - config.copyFromDtoH(type->config); - types_.push_back(Type{config, type}); + types_.push_back(type); } TypeSpecPtr Build() { // Check for overflow. - if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) { + using ElementType = const ResTable_type*; + if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) < + types_.size()) { return {}; } - TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type))); + TypeSpec* type_spec = + (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); type_spec->type_spec = header_; type_spec->idmap_entries = idmap_header_; type_spec->type_count = types_.size(); - memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type)); + memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); return TypeSpecPtr(type_spec); } @@ -118,7 +81,7 @@ class TypeSpecPtrBuilder { const ResTable_typeSpec* header_; const IdmapEntry_header* idmap_header_; - std::vector<Type> types_; + std::vector<const ResTable_type*> types_; }; } // namespace @@ -162,18 +125,17 @@ static bool VerifyResTableType(const ResTable_type* header) { return true; } -static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset, - size_t entry_idx) { +static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) { // Check that the offset is aligned. if (entry_offset & 0x03) { - LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned."; + LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned."; return false; } // Check that the offset doesn't overflow. if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) { // Overflow in offset. - LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large."; + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large."; return false; } @@ -181,7 +143,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset entry_offset += dtohl(type->entriesStart); if (entry_offset > chunk_size - sizeof(ResTable_entry)) { - LOG(ERROR) << "Entry offset at index " << entry_idx + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large. No room for ResTable_entry."; return false; } @@ -191,13 +153,13 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset const size_t entry_size = dtohs(entry->size); if (entry_size < sizeof(*entry)) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too small."; return false; } if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too large."; return false; } @@ -205,7 +167,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset if (entry_size < sizeof(ResTable_map_entry)) { // There needs to be room for one Res_value struct. if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) { - LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx + LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset << " for type " << (int)type->id << "."; return false; } @@ -214,12 +176,12 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size); const size_t value_size = dtohs(value->size); if (value_size < sizeof(Res_value)) { - LOG(ERROR) << "Res_value at index " << entry_idx << " is too small."; + LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small."; return false; } if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) { - LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx + LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset << " is too large."; return false; } @@ -228,119 +190,76 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset const size_t map_entry_count = dtohl(map->count); size_t map_entries_start = entry_offset + entry_size; if (map_entries_start & 0x03) { - LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset."; + LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset."; return false; } // Each entry is sizeof(ResTable_map) big. if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) { - LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << "."; + LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << "."; return false; } } return true; } -bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx, - const ResTable_config& config, FindEntryResult* out_entry) const { - const ResTable_config* best_config = nullptr; - const ResTable_type* best_type = nullptr; - uint32_t best_offset = 0; - - for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) { - const Type* type = &type_spec_ptr->types[i]; - const ResTable_type* type_chunk = type->type; - - if (type->configuration.match(config) && - (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) { - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const size_t entry_count = dtohl(type_chunk->entryCount); - const size_t offsets_offset = dtohs(type_chunk->header.headerSize); - - // Check if there is the desired entry in this type. - - if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { - // This is encoded as a sparse map, so perform a binary search. - const ResTable_sparseTypeEntry* sparse_indices = - reinterpret_cast<const ResTable_sparseTypeEntry*>( - reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); - const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; - const ResTable_sparseTypeEntry* result = - std::lower_bound(sparse_indices, sparse_indices_end, entry_idx, - [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { - return dtohs(entry.idx) < entry_idx; - }); - - if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) { - // No entry found. - continue; - } - - // 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 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); +} - const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); - const uint32_t offset = dtohl(entry_offsets[entry_idx]); - if (offset == ResTable_type::NO_ENTRY) { - continue; - } +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); - // There is an entry for this resource, record it. - best_offset = offset; - } + // Check if there is the desired entry in this type. - best_config = &type->configuration; - best_type = type_chunk; + if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { + // This is encoded as a sparse map, so perform a binary search. + const ResTable_sparseTypeEntry* sparse_indices = + reinterpret_cast<const ResTable_sparseTypeEntry*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; + const ResTable_sparseTypeEntry* result = + std::lower_bound(sparse_indices, sparse_indices_end, entry_index, + [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { + return dtohs(entry.idx) < entry_idx; + }); + + if (result == sparse_indices_end || dtohs(result->idx) != entry_index) { + // No entry found. + return ResTable_type::NO_ENTRY; } - } - if (best_type == nullptr) { - return false; + // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as + // the real offset divided by 4. + return uint32_t{dtohs(result->offset)} * 4u; } - if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) { - return false; + // This type is encoded as a dense array. + if (entry_index >= entry_count) { + // This entry cannot be here. + return ResTable_type::NO_ENTRY; } - const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>( - reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart)); - - const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1); - out_entry->type_flags = dtohl(flags[entry_idx]); - out_entry->entry = best_entry; - out_entry->config = best_config; - out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1); - out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index)); - return true; + const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + return dtohl(entry_offsets[entry_index]); } -bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - FindEntryResult* out_entry) const { - ATRACE_CALL(); - - // If the type IDs are offset in this package, we need to take that into account when searching - // for a type. - const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_]; - if (UNLIKELY(ptr == nullptr)) { - return false; +const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk, + uint32_t offset) { + if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) { + return nullptr; } - - // If there is an IDMAP supplied with this package, translate the entry ID. - if (ptr->idmap_entries != nullptr) { - if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) { - // There is no mapping, so the resource is not meant to be in this overlay package. - return false; - } - } - return FindEntry(ptr, entry_idx, config, out_entry); + return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) + + offset + dtohl(type_chunk->entriesStart)); } void LoadedPackage::CollectConfigurations(bool exclude_mipmap, @@ -348,7 +267,7 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, const static std::u16string kMipMap = u"mipmap"; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { if (exclude_mipmap) { const int type_idx = type_spec->type_spec->id - 1; @@ -369,8 +288,11 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, } } - for (size_t j = 0; j < type_spec->type_count; j++) { - out_configs->insert(type_spec->types[j].configuration); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config config; + config.copyFromDtoH((*iter)->config); + out_configs->insert(config); } } } @@ -380,10 +302,12 @@ void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out char temp_locale[RESTABLE_MAX_LOCALE_LEN]; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { - for (size_t j = 0; j < type_spec->type_count; j++) { - const ResTable_config& configuration = type_spec->types[j].configuration; + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config configuration; + configuration.copyFromDtoH((*iter)->config); if (configuration.locale != 0) { configuration.getBcp47Locale(temp_locale, canonicalize); std::string locale(temp_locale); @@ -411,17 +335,17 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } - for (size_t ti = 0; ti < type_spec->type_count; ti++) { - const Type* type = &type_spec->types[ti]; - size_t entry_count = dtohl(type->type->entryCount); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + const ResTable_type* type = *iter; + size_t entry_count = dtohl(type->entryCount); for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize)); + reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize)); const uint32_t offset = dtohl(entry_offsets[entry_idx]); if (offset != ResTable_type::NO_ENTRY) { - const ResTable_entry* entry = - reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) + - dtohl(type->type->entriesStart) + offset); + const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset); if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). @@ -433,8 +357,7 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } -const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const { - const uint8_t package_id = get_package_id(resid); +const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { for (const auto& loaded_package : packages_) { if (loaded_package->GetPackageId() == package_id) { return loaded_package.get(); @@ -486,14 +409,10 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } // 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, } 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 @@ std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data, 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 b033137b4764..ef08897d997a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -69,6 +69,8 @@ struct ResolvedBag { Entry entries[0]; }; +struct FindEntryResult; + // AssetManager2 is the main entry point for accessing assets and resources. // AssetManager2 provides caching of resources retrieved via the underlying ApkAssets. class AssetManager2 { @@ -127,7 +129,7 @@ class AssetManager2 { // If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap' // will be excluded from the list. std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false, - bool exclude_mipmap = false); + bool exclude_mipmap = false) const; // Returns all the locales for which there are resources defined. This includes resource // locales in all the ApkAssets set for this AssetManager. @@ -136,24 +138,24 @@ class AssetManager2 { // If `merge_equivalent_languages` is set to true, resource locales will be canonicalized // and de-duped in the resulting list. std::set<std::string> GetResourceLocales(bool exclude_system = false, - bool merge_equivalent_languages = false); + bool merge_equivalent_languages = false) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found located // in the assets/ directory. // `mode` controls how the file is opened. // // NOTE: The loaded APKs are searched in reverse order. - std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode); + std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const; // Opens a file within the assets/ directory of the APK specified by `cookie`. // `mode` controls how the file is opened. std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded. // The entries are sorted by their ASCII name. - std::unique_ptr<AssetDir> OpenDir(const std::string& dirname); + std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found. // `mode` controls how the file is opened. @@ -161,24 +163,24 @@ class AssetManager2 { // // NOTE: The loaded APKs are searched in reverse order. std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie = nullptr); + ApkAssetsCookie* out_cookie = nullptr) const; // Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened. // This is typically used to open a specific AndroidManifest.xml, or a binary XML file // referenced by a resource lookup with GetResource(). std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Populates the `out_name` parameter with resource name information. // Utf8 strings are preferred, and only if they are unavailable are // the Utf16 variants populated. // Returns false if the resource was not found or the name was missing/corrupt. - bool GetResourceName(uint32_t resid, ResourceName* out_name); + bool GetResourceName(uint32_t resid, ResourceName* out_name) const; // Populates `out_flags` with the bitmask of configuration axis that this resource varies with. // See ResTable_config for the list of configuration axis. // Returns false if the resource was not found. - bool GetResourceFlags(uint32_t resid, uint32_t* out_flags); + bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const; // Finds the resource ID assigned to `resource_name`. // `resource_name` must be of the form '[package:][type/]entry'. @@ -186,7 +188,7 @@ class AssetManager2 { // If no type is specified in `resource_name`, then `fallback_type` is used as the type. // Returns 0x0 if no resource by that name was found. uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {}, - const std::string& fallback_package = {}); + const std::string& fallback_package = {}) const; // Retrieves the best matching resource with ID `resid`. The resource value is filled into // `out_value` and the configuration for the selected value is populated in `out_selected_config`. @@ -199,7 +201,7 @@ class AssetManager2 { // this function logs if the resource was a map/bag type before returning kInvalidCookie. ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags); + uint32_t* out_flags) const; // Resolves the resource reference in `in_out_value` if the data type is // Res_value::TYPE_REFERENCE. @@ -215,7 +217,7 @@ class AssetManager2 { // it was not found. ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference); + uint32_t* out_last_reference) const; // Retrieves the best matching bag/map resource with ID `resid`. // This method will resolve all parent references for this bag and merge keys with the child. @@ -233,9 +235,9 @@ class AssetManager2 { std::unique_ptr<Theme> NewTheme(); template <typename Func> - void ForEachPackage(Func func) { + void ForEachPackage(Func func) const { for (const PackageGroup& package_group : package_groups_) { - func(package_group.packages_.front()->GetPackageName(), + func(package_group.packages_.front().loaded_package_->GetPackageName(), package_group.dynamic_ref_table.mAssignedPackageId); } } @@ -260,7 +262,7 @@ class AssetManager2 { // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds. ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match, - FindEntryResult* out_entry); + FindEntryResult* out_entry) const; // Assigns package IDs to all shared library ApkAssets. // Should be called whenever the ApkAssets are changed. @@ -270,13 +272,43 @@ class AssetManager2 { // bitmask `diff`. void InvalidateCaches(uint32_t diff); + // Triggers the re-construction of lists of types that match the set configuration. + // This should always be called when mutating the AssetManager's configuration or ApkAssets set. + void RebuildFilterList(); + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; + // A collection of configurations and their associated ResTable_type that match the current + // AssetManager configuration. + struct FilteredConfigGroup { + std::vector<ResTable_config> configurations; + std::vector<const ResTable_type*> types; + }; + + // Represents an single package. + struct ConfiguredPackage { + // A pointer to the immutable, loaded package info. + const LoadedPackage* loaded_package_; + + // A mutable AssetManager-specific list of configurations that match the AssetManager's + // current configuration. This is used as an optimization to avoid checking every single + // candidate configuration when looking up resources. + ByteBucketArray<FilteredConfigGroup> filtered_configs_; + }; + + // Represents a logical package, which can be made up of many individual packages. Each package + // in a PackageGroup shares the same package name and package ID. struct PackageGroup { - std::vector<const LoadedPackage*> packages_; + // The set of packages that make-up this group. + std::vector<ConfiguredPackage> packages_; + + // The cookies associated with each package in the group. They share the same order as + // packages_. std::vector<ApkAssetsCookie> cookies_; + + // A library reference table that contains build-package ID to runtime-package ID mappings. DynamicRefTable dynamic_ref_table; }; @@ -350,7 +382,7 @@ class Theme { ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config = nullptr, uint32_t* in_out_type_spec_flags = nullptr, - uint32_t* out_last_ref = nullptr); + uint32_t* out_last_ref = nullptr) const; private: DISALLOW_COPY_AND_ASSIGN(Theme); diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h index f281921824e7..03fad4947dfe 100644 --- a/libs/androidfw/include/androidfw/AttributeFinder.h +++ b/libs/androidfw/include/androidfw/AttributeFinder.h @@ -58,6 +58,7 @@ class BackTrackingAttributeFinder { BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end); Iterator Find(uint32_t attr); + inline Iterator end(); private: void JumpToClosestAttribute(uint32_t package_id); @@ -201,6 +202,11 @@ Iterator BackTrackingAttributeFinder<Derived, Iterator>::Find(uint32_t attr) { return end_; } +template <typename Derived, typename Iterator> +Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() { + return end_; +} + } // namespace android #endif // ANDROIDFW_ATTRIBUTE_FINDER_H diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h index 69b760414846..35ef98d8c704 100644 --- a/libs/androidfw/include/androidfw/AttributeResolution.h +++ b/libs/androidfw/include/androidfw/AttributeResolution.h @@ -17,7 +17,8 @@ #ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H #define ANDROIDFW_ATTRIBUTERESOLUTION_H -#include <androidfw/ResourceTypes.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceTypes.h" namespace android { @@ -42,19 +43,19 @@ enum { // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid, uint32_t* src_values, size_t src_values_length, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` is NOT optional and must NOT be nullptr. -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs, +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); } // namespace android diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 965e2dbd2fb2..35ae5fcd9e7b 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -41,32 +41,40 @@ class DynamicPackageEntry { int package_id = 0; }; -struct FindEntryResult { - // A pointer to the resource table entry for this resource. - // If the size of the entry is > sizeof(ResTable_entry), it can be cast to - // a ResTable_map_entry and processed as a bag/map. - const ResTable_entry* entry = nullptr; - - // The configuration for which the resulting entry was defined. - const ResTable_config* config = nullptr; - - // Stores the resulting bitmask of configuration axis with which the resource value varies. - uint32_t type_flags = 0u; - - // The dynamic package ID map for the package from which this resource came from. - const DynamicRefTable* dynamic_ref_table = nullptr; - - // The string pool reference to the type's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef type_string_ref; - - // The string pool reference to the entry's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef entry_string_ref; +// TypeSpec is going to be immediately proceeded by +// an array of Type structs, all in the same block of memory. +struct TypeSpec { + // Pointer to the mmapped data where flags are kept. + // Flags denote whether the resource entry is public + // and under which configurations it varies. + const ResTable_typeSpec* type_spec; + + // Pointer to the mmapped data where the IDMAP mappings for this type + // exist. May be nullptr if no IDMAP exists. + const IdmapEntry_header* idmap_entries; + + // The number of types that follow this struct. + // There is a type for each configuration that entries are defined for. + size_t type_count; + + // Trick to easily access a variable number of Type structs + // proceeding this struct, and to ensure their alignment. + const ResTable_type* types[0]; + + inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const { + if (entry_index >= dtohl(type_spec->entryCount)) { + return 0u; + } + + const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1); + return flags[entry_index]; + } }; -struct TypeSpec; -class LoadedArsc; +// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of +// ResTable_type pointers. +// TypeSpecPtr is a managed pointer that knows how to delete itself. +using TypeSpecPtr = util::unique_cptr<TypeSpec>; class LoadedPackage { public: @@ -76,9 +84,6 @@ class LoadedPackage { ~LoadedPackage(); - bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - FindEntryResult* out_entry) const; - // Finds the entry with the specified type name and entry name. The names are in UTF-16 because // the underlying ResStringPool API expects this. For now this is acceptable, but since // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. @@ -86,6 +91,12 @@ class LoadedPackage { // for patching the correct package ID to the resource ID. uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const; + static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index); + + static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index); + + static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset); + // Returns the string pool where type names are stored. inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; @@ -135,14 +146,32 @@ class LoadedPackage { // before being inserted into the set. This may cause some equivalent locales to de-dupe. void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const; + // type_idx is TT - 1 from 0xPPTTEEEE. + inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const { + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + return type_specs_[type_index - type_id_offset_].get(); + } + + template <typename Func> + void ForEachTypeSpec(Func f) const { + for (size_t i = 0; i < type_specs_.size(); i++) { + const TypeSpecPtr& ptr = type_specs_[i]; + if (ptr != nullptr) { + uint8_t type_id = ptr->type_spec->id; + if (ptr->idmap_entries != nullptr) { + type_id = ptr->idmap_entries->target_type_id; + } + f(ptr.get(), type_id - 1); + } + } + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); LoadedPackage(); - bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx, - const ResTable_config& config, FindEntryResult* out_entry) const; - ResStringPool type_string_pool_; ResStringPool key_string_pool_; std::string package_name_; @@ -152,7 +181,7 @@ class LoadedPackage { bool system_ = false; bool overlay_ = false; - ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_; + ByteBucketArray<TypeSpecPtr> type_specs_; std::vector<DynamicPackageEntry> dynamic_package_map_; }; @@ -180,25 +209,20 @@ class LoadedArsc { return &global_string_pool_; } - // Finds the resource with ID `resid` with the best value for configuration `config`. - // The parameter `out_entry` will be filled with the resulting resource entry. - // The resource entry can be a simple entry (ResTable_entry) or a complex bag - // (ResTable_entry_map). - bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const; + // Gets a pointer to the package with the specified package ID, or nullptr if no such package + // exists. + const LoadedPackage* GetPackageById(uint8_t package_id) const; - // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist. - const LoadedPackage* GetPackageForId(uint32_t resid) const; + // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. + inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { + return packages_; + } // Returns true if this is a system provided resource. inline bool IsSystem() const { return system_; } - // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. - inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { - return packages_; - } - private: DISALLOW_COPY_AND_ASSIGN(LoadedArsc); diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h new file mode 100644 index 000000000000..64924f433245 --- /dev/null +++ b/libs/androidfw/include/androidfw/MutexGuard.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROIDFW_MUTEXGUARD_H +#define ANDROIDFW_MUTEXGUARD_H + +#include <mutex> +#include <type_traits> + +#include "android-base/macros.h" + +namespace android { + +template <typename T> +class ScopedLock; + +// Owns the guarded object and protects access to it via a mutex. +// The guarded object is inaccessible via this class. +// The mutex is locked and the object accessed via the ScopedLock<T> class. +// +// NOTE: The template parameter T should not be a raw pointer, since ownership +// is ambiguous and error-prone. Instead use an std::unique_ptr<>. +// +// Example use: +// +// Guarded<std::string> shared_string("hello"); +// { +// ScopedLock<std::string> locked_string(shared_string); +// *locked_string += " world"; +// } +// +template <typename T> +class Guarded { + static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer"); + + public: + explicit Guarded() : guarded_() { + } + + template <typename U = T> + explicit Guarded(const T& guarded, + typename std::enable_if<std::is_copy_constructible<U>::value>::type = void()) + : guarded_(guarded) { + } + + template <typename U = T> + explicit Guarded(T&& guarded, + typename std::enable_if<std::is_move_constructible<U>::value>::type = void()) + : guarded_(std::move(guarded)) { + } + + private: + friend class ScopedLock<T>; + + DISALLOW_COPY_AND_ASSIGN(Guarded); + + std::mutex lock_; + T guarded_; +}; + +template <typename T> +class ScopedLock { + public: + explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) { + } + + T& operator*() { + return guarded_; + } + + T* operator->() { + return &guarded_; + } + + T* get() { + return &guarded_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedLock); + + std::lock_guard<std::mutex> lock_; + T& guarded_; +}; + +} // namespace android + +#endif // ANDROIDFW_MUTEXGUARD_H diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index c2eae855bb7b..d94779bf5225 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,7 +28,7 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin StringPiece* out_entry); inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) { - return resid | (static_cast<uint32_t>(package_id) << 24); + return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24); } inline uint8_t get_package_id(uint32_t resid) { diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index 6c43a67e602f..e2b9f0040989 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -26,58 +26,56 @@ using ::android::base::unique_fd; using ::com::android::basic::R; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; namespace android { TEST(ApkAssetsTest, LoadApk) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - - const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000); - ASSERT_NE(nullptr, loaded_package); - - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkFromFd) { const std::string path = GetTestDataPath() + "/basic/basic.apk"; unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY)); - ASSERT_GE(fd.get(), 0); + ASSERT_THAT(fd.get(), Ge(0)); std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - - const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000); - ASSERT_NE(nullptr, loaded_package); - - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkAsSharedLibrary) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic()); loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic()); } @@ -86,19 +84,22 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) { ResTable target_table; const std::string target_path = GetTestDataPath() + "/basic/basic.apk"; ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/)); + ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); ResTable overlay_table; const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk"; ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/)); + ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); util::unique_cptr<void> idmap_data; void* temp_data; size_t idmap_len; - ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), - overlay_path.c_str(), &temp_data, &idmap_len)); + ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), + overlay_path.c_str(), &temp_data, &idmap_len), + Eq(NO_ERROR)); idmap_data.reset(temp_data); TemporaryFile tf; @@ -108,37 +109,30 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) { // Open something so that the destructor of TemporaryFile closes a valid fd. tf.fd = open("/dev/null", O_WRONLY); - std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path); - ASSERT_NE(nullptr, loaded_overlay_apk); + ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull()); } TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } } TEST(ApkAssetsTest, OpenUncompressedAssetFd) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(asset, NotNull()); off64_t start, length; unique_fd fd(asset->openFileDescriptor(&start, &length)); - EXPECT_GE(fd.get(), 0); + ASSERT_THAT(fd.get(), Ge(0)); lseek64(fd.get(), start, SEEK_SET); @@ -146,7 +140,7 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) { buffer.resize(length); ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length)); - EXPECT_EQ("This should be uncompressed.\n\n", buffer); + EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n")); } } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 85e8f25394e9..437e14772964 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -81,17 +81,18 @@ static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld); -static void BM_AssetManagerGetResource(benchmark::State& state) { - GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) { + GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state); } -BENCHMARK(BM_AssetManagerGetResource); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref); -static void BM_AssetManagerGetResourceOld(benchmark::State& state) { - GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) { + GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, + state); } -BENCHMARK(BM_AssetManagerGetResourceOld); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref); static void BM_AssetManagerGetLibraryResource(benchmark::State& state) { GetResourceBenchmark( @@ -196,7 +197,7 @@ BENCHMARK(BM_AssetManagerGetResourceLocales); static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { AssetManager assets; if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, - false /*isSystemAssets*/)) { + true /*isSystemAssets*/)) { state.SkipWithError("Failed to load assets"); return; } @@ -211,4 +212,44 @@ static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerGetResourceLocalesOld); +static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath); + if (apk == nullptr) { + state.SkipWithError("Failed to load assets"); + return; + } + + AssetManager2 assets; + assets.SetApkAssets({apk.get()}); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.SetConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFramework); + +static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) { + AssetManager assets; + if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, + true /*isSystemAssets*/)) { + state.SkipWithError("Failed to load assets"); + return; + } + + const ResTable& table = assets.getResources(true); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.setConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld); + } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 92462a6cfadf..7cac2b3417b5 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -36,6 +36,10 @@ namespace lib_one = com::android::lib_one; 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 @@ class AssetManager2Test : public ::testing::Test { libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk"); ASSERT_NE(nullptr, libclient_assets_); - appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); + appaslib_assets_ = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); ASSERT_NE(nullptr, appaslib_assets_); system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/); ASSERT_NE(nullptr, system_assets_); + + app_assets_ = ApkAssets::Load(GetTestDataPath() + "/app/app.apk"); + ASSERT_THAT(app_assets_, NotNull()); } protected: @@ -75,6 +82,7 @@ class AssetManager2Test : public ::testing::Test { 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 @@ TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) { assetmanager.SetApkAssets( {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + const ResolvedBag* bag = assetmanager.GetBag(fix_package_id(lib_one::R::style::Theme, 0x03)); + ASSERT_NE(nullptr, bag); + ASSERT_GE(bag->entry_count, 2u); + + // First two attributes come from lib_one. + EXPECT_EQ(1, bag->entries[0].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[0].key)); + EXPECT_EQ(1, bag->entries[1].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); +} + +TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme); ASSERT_NE(nullptr, bag); ASSERT_GE(bag->entry_count, 2u); @@ -446,8 +473,68 @@ TEST_F(AssetManager2Test, GetResourceId) { assetmanager.GetResourceId("main", "layout", "com.android.basic")); } -TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {} +TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({system_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("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)); -TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {} + 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 000000000000..fa300c50218a --- /dev/null +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "benchmark/benchmark.h" + +//#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/AttributeResolution.h" +#include "androidfw/ResourceTypes.h" + +#include "BenchmarkHelpers.h" +#include "data/basic/R.h" +#include "data/styles/R.h" + +namespace app = com::android::app; +namespace basic = com::android::basic; + +namespace android { + +constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk"; +constexpr const static uint32_t Theme_Material_Light = 0x01030237u; + +static void BM_ApplyStyle(benchmark::State& state) { + std::unique_ptr<const ApkAssets> styles_apk = + ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + if (styles_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({styles_apk.get()}); + + std::unique_ptr<Asset> asset = + assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(app::R::style::StyleTwo); + + std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two, + app::R::attr::attr_three, app::R::attr::attr_four, + app::R::attr::attr_five, app::R::attr::attr_empty}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), + attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyle); + +static void BM_ApplyStyleFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath); + if (framework_apk == nullptr) { + state.SkipWithError("failed to load framework assets"); + return; + } + + std::unique_ptr<const ApkAssets> basic_apk = + ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); + if (basic_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()}); + + ResTable_config device_config; + memset(&device_config, 0, sizeof(device_config)); + device_config.language[0] = 'e'; + device_config.language[1] = 'n'; + device_config.country[0] = 'U'; + device_config.country[1] = 'S'; + device_config.orientation = ResTable_config::ORIENTATION_PORT; + device_config.smallestScreenWidthDp = 700; + device_config.screenWidthDp = 700; + device_config.screenHeightDp = 1024; + device_config.sdkVersion = 27; + + Res_value value; + ResTable_config config; + uint32_t flags = 0u; + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/, + 0u /*density_override*/, &value, &config, &flags); + if (cookie == kInvalidCookie) { + state.SkipWithError("failed to find R.layout.layout"); + return; + } + + size_t len = 0u; + const char* layout_path = + assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len); + if (layout_path == nullptr || len == 0u) { + state.SkipWithError("failed to lookup layout path"); + return; + } + + std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset( + StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(Theme_Material_Light); + + std::array<uint32_t, 92> attrs{ + {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099, + 0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f, + 0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151, + 0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158, + 0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f, + 0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166, + 0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d, + 0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d, + 0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5, + 0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f, + 0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d, + 0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df, + 0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9, + 0x011100ca}}; + + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/, + attrs.data(), attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyleFramework); + +} // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp index 2d73ce8f8ee3..c8dbe205fee2 100644 --- a/libs/androidfw/tests/AttributeResolution_test.cpp +++ b/libs/androidfw/tests/AttributeResolution_test.cpp @@ -21,6 +21,8 @@ #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/macros.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceUtils.h" #include "TestHelpers.h" #include "data/styles/R.h" @@ -32,15 +34,14 @@ namespace android { class AttributeResolutionTest : public ::testing::Test { public: virtual void SetUp() override { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString( - GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(), - 1 /*cookie*/, true /*copyData*/)); + styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, styles_assets_); + assetmanager_.SetApkAssets({styles_assets_.get()}); } protected: - ResTable table_; + std::unique_ptr<const ApkAssets> styles_assets_; + AssetManager2 assetmanager_; }; class AttributeResolutionXmlTest : public AttributeResolutionTest { @@ -48,13 +49,12 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { virtual void SetUp() override { AttributeResolutionTest::SetUp(); - std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", - "res/layout/layout.xml", &contents)); + std::unique_ptr<Asset> asset = + assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + ASSERT_NE(nullptr, asset); - ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(), - true /*copyData*/)); + ASSERT_EQ(NO_ERROR, + xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/)); // Skip to the first tag. while (xml_parser_.next() != ResXMLParser::START_TAG) { @@ -65,15 +65,50 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { ResXMLTree xml_parser_; }; +TEST(AttributeResolutionLibraryTest, ApplyStyleWithDefaultStyleResId) { + AssetManager2 assetmanager; + auto apk_assets = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, apk_assets); + assetmanager.SetApkAssets({apk_assets.get()}); + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + + std::array<uint32_t, 2> attrs{ + {fix_package_id(R::attr::attr_one, 0x02), fix_package_id(R::attr::attr_two, 0x02)}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + ApplyStyle(theme.get(), nullptr /*xml_parser*/, 0u /*def_style_attr*/, + fix_package_id(R::style::StyleOne, 0x02), attrs.data(), attrs.size(), values.data(), + indices.data()); + + const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; + + const uint32_t* values_cursor = values.data(); + EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]); + EXPECT_EQ(1u, values_cursor[STYLE_DATA]); + EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]); + EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]); + EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]); + EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]); + + values_cursor += STYLE_NUM_ENTRIES; + EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]); + EXPECT_EQ(2u, values_cursor[STYLE_DATA]); + EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]); + EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]); + EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]); + EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]); +} + TEST_F(AttributeResolutionTest, Theme) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/, + ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/, nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(), attrs.size(), values.data(), nullptr /*out_indices*/)); @@ -126,8 +161,8 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(), - nullptr /*out_indices*/)); + ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(), + values.data(), nullptr /*out_indices*/)); uint32_t* values_cursor = values.data(); EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]); @@ -171,15 +206,15 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { } TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; std::array<uint32_t, attrs.size() + 1> indices; - ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(), + ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), attrs.size(), values.data(), indices.data()); const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index 7149beef797f..faddfe599af4 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -33,19 +33,21 @@ void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTab } } + // Make sure to force creation of the ResTable first, or else the configuration doesn't get set. + const ResTable& table = assetmanager.getResources(true); if (config != nullptr) { assetmanager.setConfiguration(*config); } - const ResTable& table = assetmanager.getResources(true); - Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_ref = 0u; while (state.KeepRunning()) { - table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, - &selected_config); + ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, + &selected_config); + table.resolveReference(&value, block, &last_ref, &flags, &selected_config); } } @@ -72,10 +74,12 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_ Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_id = 0u; while (state.KeepRunning()) { - assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value, - &selected_config, &flags); + ApkAssetsCookie cookie = assetmanager.GetResource( + resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags); + assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id); } } diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 37ddafb14fd3..cae632ddea30 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 basic = com::android::basic; 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 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); - - const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); - - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 24; + ASSERT_THAT(loaded_arsc, NotNull()); - FindEntryResult entry; - - 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)); + 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)); - std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + 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); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - desired_config.language[0] = 'd'; - desired_config.language[1] = 'e'; + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); - 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 @@ TEST(LoadedArscTest, LoadSparseEntryApp) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9)); + ASSERT_THAT(package, NotNull()); + + 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); - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, 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, LoadSharedLibrary) { @@ -93,14 +96,13 @@ TEST(LoadedArscTest, LoadSharedLibrary) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName()); - EXPECT_EQ(0, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); @@ -114,25 +116,23 @@ TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) { "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_FALSE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); // The library has two dependencies. - ASSERT_EQ(2u, dynamic_pkg_map.size()); + ASSERT_THAT(dynamic_pkg_map, SizeIs(2u)); + EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one")); + EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02)); - EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name); - EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id); - - EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name); - EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id); + EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two")); + EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03)); } TEST(LoadedArscTest, LoadAppAsSharedLibrary) { @@ -143,13 +143,12 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) { std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/, true /*load_as_shared_library*/); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); } TEST(LoadedArscTest, LoadFeatureSplit) { @@ -157,21 +156,67 @@ TEST(LoadedArscTest, LoadFeatureSplit) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3)); + ASSERT_THAT(package, NotNull()); - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry)); + uint8_t type_index = get_type_id(basic::R::string::test3) - 1; + uint8_t entry_index = get_entry_id(basic::R::string::test3); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); size_t len; - const char16_t* type_name16 = entry.type_string_ref.string16(&len); - ASSERT_NE(nullptr, type_name16); - ASSERT_NE(0u, len); + const char16_t* type_name16 = + package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len); + ASSERT_THAT(type_name16, NotNull()); + EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string")); - std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len)); - EXPECT_EQ(std::string("string"), type_name); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull()); +} + +// 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 @@ class MockLoadedIdmap : public LoadedIdmap { }; TEST(LoadedArscTest, LoadOverlay) { - std::string contents, overlay_contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc", - &overlay_contents)); + &contents)); MockLoadedIdmap loaded_idmap; std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap); - ASSERT_NE(nullptr, loaded_arsc); - - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry)); + LoadedArsc::Load(StringPiece(contents), &loaded_idmap); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u); + ASSERT_THAT(package, NotNull()); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); + + // The entry being overlaid doesn't exist at the original entry index. + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull()); + + // Since this is an overlay, the actual entry ID must be mapped. + ASSERT_THAT(type_spec->idmap_entries, NotNull()); + uint16_t target_entry_id = 0u; + ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id)); + ASSERT_THAT(target_entry_id, Eq(0x0u)); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull()); } // structs with size fields (like Res_value, ResTable_entry) should be diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h index 43a995536d89..df0c642f4565 100644 --- a/libs/androidfw/tests/TestHelpers.h +++ b/libs/androidfw/tests/TestHelpers.h @@ -20,6 +20,7 @@ #include <string> #include "androidfw/ResourceTypes.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "CommonHelpers.h" diff --git a/libs/androidfw/tests/data/app/app.apk b/libs/androidfw/tests/data/app/app.apk Binary files differindex ccb08242a656..c8ad86ded851 100644 --- a/libs/androidfw/tests/data/app/app.apk +++ b/libs/androidfw/tests/data/app/app.apk 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 000000000000..b214e06d6ece --- /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 000000000000..081154272520 --- /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 d418158c547b..09af842e70fb 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 94a2a14ced87..b7e814fea079 100644 --- a/libs/androidfw/tests/data/basic/R.h +++ b/libs/androidfw/tests/data/basic/R.h @@ -34,6 +34,7 @@ struct R { struct layout { enum : uint32_t { main = 0x7f020000, + layoutt = 0x7f020001, }; }; @@ -55,6 +56,7 @@ struct R { number2 = 0x7f040001, ref1 = 0x7f040002, ref2 = 0x7f040003, + deep_ref = 0x7f040004, // From feature number3 = 0x80030000, diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk Binary files differindex 18ef75e91ded..1733b6a16546 100644 --- a/libs/androidfw/tests/data/basic/basic.apk +++ b/libs/androidfw/tests/data/basic/basic.apk diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml new file mode 100644 index 000000000000..045ede454bca --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/ok" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginStart="2dip" + android:layout_marginEnd="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:text="@android:string/ok" />
\ No newline at end of file diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml index 6c474596b5cd..b3435629265b 100644 --- a/libs/androidfw/tests/data/basic/res/values/values.xml +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -22,6 +22,7 @@ <attr name="attr2" format="reference|integer" /> <public type="layout" name="main" id="0x7f020000" /> + <public type="layout" name="layout" id="0x7f020001" /> <public type="string" name="test1" id="0x7f030000" /> <string name="test1">test1</string> @@ -43,6 +44,18 @@ <public type="integer" name="ref2" id="0x7f040003" /> <integer name="ref2">12000</integer> + <public type="integer" name="deep_ref" id="0x7f040004" /> + <integer name="deep_ref">@integer/deep_ref_1</integer> + <integer name="deep_ref_1">@integer/deep_ref_2</integer> + <integer name="deep_ref_2">@integer/deep_ref_3</integer> + <integer name="deep_ref_3">@integer/deep_ref_4</integer> + <integer name="deep_ref_4">@integer/deep_ref_5</integer> + <integer name="deep_ref_5">@integer/deep_ref_6</integer> + <integer name="deep_ref_6">@integer/deep_ref_7</integer> + <integer name="deep_ref_7">@integer/deep_ref_8</integer> + <integer name="deep_ref_8">@integer/deep_ref_9</integer> + <integer name="deep_ref_9">100</integer> + <public type="style" name="Theme1" id="0x7f050000" /> <style name="Theme1"> <item name="com.android.basic:attr1">100</item> diff --git a/libs/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 000000000000..34016db8b808 --- /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 000000000000..8496f81038b0 --- /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 000000000000..eca8f478c501 --- /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 Binary files differnew file mode 100644 index 000000000000..75146e0fc476 --- /dev/null +++ b/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk 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 000000000000..7c54fbae9f21 --- /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 000000000000..f73f3093ff86 --- /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 000000000000..3f74eb6e6441 --- /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 bfbdf4ca770b..b65145a8454f 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 b97bdb68aca7..5f60d214c744 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 35d43c77fc7a..7893c946e299 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 Binary files differindex 1299016a0f83..9045d6c4de21 100644 --- a/libs/androidfw/tests/data/system/system.apk +++ b/libs/androidfw/tests/data/system/system.apk diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index b2edd3392873..2b0b22df7edf 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -187,11 +187,6 @@ void SkiaCanvas::restoreToCount(int restoreCount) { static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) { SkCanvas::SaveLayerFlags layerFlags = 0; - // We intentionally ignore the SaveFlags::HasAlphaLayer and - // SkCanvas::kIsOpaque_SaveLayerFlag flags because HWUI ignores it - // and our Android client may use it incorrectly. - // In Skia, this flag is purely for performance optimization. - if (!(flags & SaveFlags::ClipToLayer)) { layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag; } @@ -747,6 +742,12 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& p 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 06e2d6c00ef5..fc009d871620 100644 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -204,10 +204,6 @@ static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) { saveFlags |= SaveFlags::ClipToLayer; } - if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) { - saveFlags |= SaveFlags::HasAlphaLayer; - } - return saveFlags; } diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index f118e8d0a53e..f662406bdc38 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -557,13 +557,12 @@ void Tree::Cache::clear() { 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 @@ void Tree::draw(SkCanvas* canvas) { 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 d9cf8ab7eedf..da52a9503377 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -644,7 +644,7 @@ public: * 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/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 284fd836745c..ad4c8be74d75 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -225,4 +225,10 @@ void Canvas::drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiF 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 3ddf1c48b83f..fabb8d2e4549 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -116,6 +116,14 @@ public: 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 @@ protected: 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 091b5267881d..dca9ef5559a6 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -132,8 +132,8 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font 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 @@ void Typeface::setRobotoTypefaceForTest() { 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 eabe2e87fc49..25c76eb4db3e 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -124,14 +124,19 @@ void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor, 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 2953ea8b21e9..15c0ab1ad8d2 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -471,6 +471,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) { 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 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { 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 8fdb0e347afa..42a92fcc7d89 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -250,6 +250,7 @@ public: 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 2232c25de345..e424a266bf72 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -57,7 +57,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { 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 d3c6eddd02eb..d194796ec1c5 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1271,6 +1271,9 @@ public class LocationManager { 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/AudioRecord.java b/media/java/android/media/AudioRecord.java index d0963cbcb87f..384753018bc8 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -1416,6 +1416,7 @@ public class AudioRecord implements AudioRouting /* * Call BEFORE adding a routing callback handler. */ + @GuardedBy("mRoutingChangeListeners") private void testEnableNativeRoutingCallbacksLocked() { if (mRoutingChangeListeners.size() == 0) { native_enableDeviceCallback(); @@ -1425,6 +1426,7 @@ public class AudioRecord implements AudioRouting /* * 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 8e822a5d7a37..2d5fad5dde5b 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2821,6 +2821,7 @@ public class AudioTrack extends PlayerBase /* * Call BEFORE adding a routing callback handler. */ + @GuardedBy("mRoutingChangeListeners") private void testEnableNativeRoutingCallbacksLocked() { if (mRoutingChangeListeners.size() == 0) { native_enableDeviceCallback(); @@ -2830,6 +2831,7 @@ public class AudioTrack extends PlayerBase /* * 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/MediaController2.java b/media/java/android/media/MediaController2.java index e9ffe60ec32d..bd6c7e6bed88 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -30,7 +30,7 @@ import android.media.MediaSession2.PlaylistParams; 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 948da0b97910..4ba2120f5b09 100644 --- a/media/java/android/media/MediaDataSource.java +++ b/media/java/android/media/MediaDataSource.java @@ -34,8 +34,8 @@ public abstract class MediaDataSource implements Closeable { /** * 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 1bc3dfa4bdbc..fe5e8226e159 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1484,6 +1484,7 @@ public class MediaPlayer extends PlayerBase /* * 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 e3d5ac07665e..d4e9aacc42ea 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -1417,6 +1417,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { /* * 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 62240cedf0be..823410f6bb76 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -1353,6 +1353,7 @@ public class MediaRecorder implements AudioRouting /* * 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 bbcce82f2e2d..bd6667faff31 100644 --- a/media/java/android/media/VolumePolicy.java +++ b/media/java/android/media/VolumePolicy.java @@ -23,7 +23,7 @@ import java.util.Objects; /** @hide */ public final class VolumePolicy implements Parcelable { - public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, true, 400); + public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, false, 400); /** * Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java index 71bc64a62abf..c492d3072c78 100644 --- a/media/java/android/media/update/MediaController2Provider.java +++ b/media/java/android/media/update/MediaController2Provider.java @@ -18,6 +18,7 @@ package android.media.update; 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 @@ public interface MediaController2Provider extends TransportControlProvider { 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/native/android/asset_manager.cpp b/native/android/asset_manager.cpp index 98e9a42d944d..e70d5ea0d566 100644 --- a/native/android/asset_manager.cpp +++ b/native/android/asset_manager.cpp @@ -18,9 +18,11 @@ #include <utils/Log.h> #include <android/asset_manager_jni.h> +#include <android_runtime/android_util_AssetManager.h> #include <androidfw/Asset.h> #include <androidfw/AssetDir.h> #include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include <utils/threads.h> #include "jni.h" @@ -35,21 +37,20 @@ using namespace android; // ----- struct AAssetDir { - AssetDir* mAssetDir; + std::unique_ptr<AssetDir> mAssetDir; size_t mCurFileIndex; String8 mCachedFileName; - explicit AAssetDir(AssetDir* dir) : mAssetDir(dir), mCurFileIndex(0) { } - ~AAssetDir() { delete mAssetDir; } + explicit AAssetDir(std::unique_ptr<AssetDir> dir) : + mAssetDir(std::move(dir)), mCurFileIndex(0) { } }; // ----- struct AAsset { - Asset* mAsset; + std::unique_ptr<Asset> mAsset; - explicit AAsset(Asset* asset) : mAsset(asset) { } - ~AAsset() { delete mAsset; } + explicit AAsset(std::unique_ptr<Asset> asset) : mAsset(std::move(asset)) { } }; // -------------------- Public native C API -------------------- @@ -104,19 +105,18 @@ AAsset* AAssetManager_open(AAssetManager* amgr, const char* filename, int mode) return NULL; } - AssetManager* mgr = static_cast<AssetManager*>(amgr); - Asset* asset = mgr->open(filename, amMode); - if (asset == NULL) { - return NULL; + ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr)); + std::unique_ptr<Asset> asset = locked_mgr->Open(filename, amMode); + if (asset == nullptr) { + return nullptr; } - - return new AAsset(asset); + return new AAsset(std::move(asset)); } AAssetDir* AAssetManager_openDir(AAssetManager* amgr, const char* dirName) { - AssetManager* mgr = static_cast<AssetManager*>(amgr); - return new AAssetDir(mgr->openDir(dirName)); + ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr)); + return new AAssetDir(locked_mgr->OpenDir(dirName)); } /** diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml index 8d79f62f21d7..c0a59b3badbf 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/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index c926e1ff48a7..c1aa2dcfbdb5 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 6ef3facc1aea..e557c65ca9d3 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> @@ -865,30 +865,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] 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] 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=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> + <!-- [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$s</xliff:g> left</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> + + <!-- 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> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index fb0f75b522b3..e1ebbc4c166c 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 index cda4e454fe74..5f7ba586fbad 100755..100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -194,8 +194,13 @@ public class LocalBluetoothAdapter { return mState; } - synchronized void setBluetoothStateInt(int state) { - mState = state; + void setBluetoothStateInt(int state) { + synchronized(this) { + if (mState == state) { + return; + } + mState = state; + } if (state == BluetoothAdapter.STATE_ON) { // if mProfileManager hasn't been constructed yet, it will diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java index 4e78d9b3d33c..85bf4e83bd55 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 @@ package com.android.settingslib.development; 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 class DevelopmentSettingsEnabler { 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 class DevelopmentSettingsEnabler { } 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 190f5e6d1402..68ead09d9d57 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 @@ package com.android.settingslib.drawer; 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { @Override public boolean onNavigateUp() { - finish(); + if (!super.onNavigateUp()) { + finish(); + } return true; } @@ -104,11 +105,6 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { 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 @@ public class SettingsDrawerActivity extends Activity { ((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 class SettingsDrawerActivity extends Activity { } } - 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 ed3696cafa45..f7aa29796ce8 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 @@ public class DataUsageController { 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 class DataUsageController { 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/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java new file mode 100644 index 000000000000..346ca66bcb13 --- /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 000000000000..45fdd7860836 --- /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 109eb97ed2d8..8115ede2729e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -41,7 +41,7 @@ import com.android.settingslib.TwoTargetPreference; import com.android.settingslib.Utils; import com.android.settingslib.wifi.AccessPoint.Speed; -public class AccessPointPreference extends TwoTargetPreference { +public class AccessPointPreference extends Preference { private static final int[] STATE_SECURED = { R.attr.state_encrypted @@ -115,6 +115,7 @@ public class AccessPointPreference extends TwoTargetPreference { int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld, int level, IconInjector iconInjector) { super(context); + setWidgetLayoutResource(R.layout.access_point_friction_widget); mBadgeCache = cache; mAccessPoint = accessPoint; mForSavedNetworks = forSavedNetworks; @@ -153,20 +154,6 @@ public class AccessPointPreference extends TwoTargetPreference { ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon); bindFrictionImage(frictionImageView); - setDividerVisibility(view, View.GONE); - } - - protected void setDividerVisibility(final PreferenceViewHolder view, - @View.Visibility int visibility) { - final View divider = view.findViewById(R.id.two_target_divider); - if (divider != null) { - divider.setVisibility(visibility); - } - } - - @Override - protected int getSecondTargetResId() { - return R.layout.access_point_friction_widget; } protected void updateIcon(int level, Context context) { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java index 003f90579415..2f417adb7077 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 @@ package com.android.settingslib.drawer; 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 @@ public class SettingsDrawerActivityTest { } @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 4091ce1f173e..1481161bcb6c 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 @@ public class CachedBluetoothDeviceTest { 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 @@ public class CachedBluetoothDeviceTest { 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 @@ public class CachedBluetoothDeviceTest { // 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 @@ public class CachedBluetoothDeviceTest { 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 @@ public class CachedBluetoothDeviceTest { } @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 d19d19ab62e9..708353ebb7a0 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 android.provider.Settings; 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.RuntimeEnvironment; 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 @@ public class DevelopmentSettingsEnablerTest { @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 @@ public class DevelopmentSettingsEnablerTest { } @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 @@ public class DevelopmentSettingsEnablerTest { 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/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java new file mode 100644 index 000000000000..c8b3269da09b --- /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 000000000000..f93210f264ff --- /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 000000000000..d5e3cdb654e5 --- /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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 64b2ae6e23d3..79299aa6abcb 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 2bcf4ef9f856..1e48213c42d8 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -56,7 +56,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 9613a6a8c059..cbb3e8fcab57 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 index b3d5c9008039..12f278a204c7 100644 --- a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml +++ b/packages/SystemUI/res/layout/recents_onboarding.xml @@ -30,7 +30,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:text="@string/recents_swipe_up_onboarding" android:textColor="@android:color/white" android:textSize="16sp" android:drawableBottom="@drawable/ic_chevron_up"/> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e55c65a47f7b..a9021ae674f0 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 f9aa8216ec11..2d30f4c0082d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -47,7 +47,6 @@ <item type="id" name="qs_icon_tag"/> <item type="id" name="qs_slash_tag"/> <item type="id" name="scrim"/> - <item type="id" name="scrim_blanking"/> <item type="id" name="scrim_target"/> <item type="id" name="scrim_alpha_start"/> <item type="id" name="scrim_alpha_end"/> @@ -58,6 +57,8 @@ <item type="id" name="notification_plugin"/> <item type="id" name="transformation_start_x_tag"/> <item type="id" name="transformation_start_y_tag"/> + <item type="id" name="transformation_start_actual_width"/> + <item type="id" name="transformation_start_actual_height"/> <item type="id" name="transformation_start_scale_x_tag"/> <item type="id" name="transformation_start_scale_y_tag"/> <item type="id" name="continuous_clipping_tag"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index fadcbcd4f4bc..fc5ea4581a01 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -842,8 +842,6 @@ <string name="recents_stack_action_button_label">Clear all</string> <!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] --> <string name="recents_drag_hint_message">Drag here to use split screen</string> - <!-- Recents: Text that shows above the nav bar after launching a few apps. [CHAR LIMIT=NONE] --> - <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string> <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] --> <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 64fa9c61280a..cd9e1268f0c7 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 @@ import com.android.systemui.shared.recents.ISystemUiProxy; 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 98ede4eafbe2..4cf817e02fff 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -39,4 +39,9 @@ interface ISystemUiProxy { * Called when the overview service has started the recents animation. */ void onRecentsAnimationStarted(); + + /** + * Specifies the text to be shown for onboarding the new swipe-up gesture to access recents. + */ + void setRecentsOnboardingText(CharSequence text); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 90e3b1e73454..62bd72f393cc 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.pm.UserInfo; 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.IRecentsAnimationController; 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 @@ public class ActivityManagerWrapper { 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 bf6179d70a5e..a473db1a7a14 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 3871980a5b17..b8c5049d1c1d 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 class RemoteAnimationTargetCompat { 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 @@ public class RemoteAnimationTargetCompat { } 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 239a4e37e832..1715983d3650 100644 --- a/packages/SystemUI/shared/tests/Android.mk +++ b/packages/SystemUI/shared/tests/Android.mk @@ -41,7 +41,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 b7e1d67a5b3a..b30b0c575415 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -66,6 +66,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; + private CharSequence mOnboardingText; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -105,6 +106,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Binder.restoreCallingIdentity(token); } } + + public void setRecentsOnboardingText(CharSequence text) { + mOnboardingText = text; + } }; private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() { @@ -223,8 +228,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return mOverviewProxy; } - public ComponentName getLauncherComponent() { - return mLauncherComponentName; + public CharSequence getOnboardingText() { + return mOnboardingText; } private void disconnectFromLauncherService() { diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 9319bc60f9ef..adb4e33d1a19 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -49,7 +49,7 @@ public final class Prefs { Key.QS_NIGHTDISPLAY_ADDED, Key.SEEN_MULTI_USER, Key.NUM_APPS_LAUNCHED, - Key.HAS_SWIPED_UP_FOR_RECENTS, + Key.HAS_SEEN_RECENTS_ONBOARDING, }) public @interface Key { @Deprecated @@ -78,7 +78,7 @@ public final class Prefs { String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded"; String SEEN_MULTI_USER = "HasSeenMultiUser"; String NUM_APPS_LAUNCHED = "NumAppsLaunched"; - String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents"; + String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java index 348855bb0440..afc9629ecede 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 @@ package com.android.systemui.charging; 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 @@ import android.view.WindowManager; */ 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 @@ public class WirelessChargingAnimation { * @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 @@ public class WirelessChargingAnimation { * @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 @@ public class WirelessChargingAnimation { 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 class WirelessChargingAnimation { } 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 @@ public class WirelessChargingAnimation { 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 @@ public class WirelessChargingAnimation { 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 @@ public class WirelessChargingAnimation { 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 c78ea56524cb..8f87d647a0e3 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 @@ public class WirelessChargingLayout extends FrameLayout { 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 @@ public class WirelessChargingLayout extends FrameLayout { 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 f5edf5216fa2..19c6dc1ceb1f 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.Canvas; 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 @@ final class WirelessChargingView extends View { 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 @@ final class WirelessChargingView extends View { 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 @@ final class WirelessChargingView extends View { 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)); - - canvas.drawCircle(x, y, mSmallCircleRadius, mPaint); - } + mCurrDotRadius)); - if (mMainCircleCurrentRadius >= mMainCircleEndRadius && !isSpinCircleAnimationStarted()) { - mStartSpinCircleAnimationTime = System.currentTimeMillis(); + canvas.drawCircle(x, y, mCurrDotRadius, mPaint); } - if (isSpinAnimationFinished()) { - mFinishedAnimatingSpinningCircles = true; + if (mMainCircleCurrentRadius >= mMainCircleEndRadius) { + mFinishedAnimatingDots = 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 @@ final class WirelessChargingView extends View { * 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/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index aa56694775fc..3a2b12f4da23 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.OnClickListener; 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.Looper; 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 com.android.systemui.util.NotificationChannels; 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 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { // 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 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { 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 ea2a432ea6ca..ac86c8ae097d 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 com.android.systemui.statusbar.phone.StatusBar; 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 @@ public class PowerUI extends SystemUI { 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 @@ public class PowerUI extends SystemUI { 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 @@ public class PowerUI extends SystemUI { 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 @@ public class PowerUI extends SystemUI { 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 @@ public class PowerUI extends SystemUI { } } - 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 @@ public class PowerUI extends SystemUI { @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 @@ public class PowerUI extends SystemUI { || 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 a44fd9a0f918..7b1509dcd173 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.view.ViewParent; 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 @@ public class QSScrollLayout extends NestedScrollView { 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,23 +57,35 @@ public class QSScrollLayout extends NestedScrollView { 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 super.onInterceptTouchEvent(ev); + if (!canScrollVertically(1) && !canScrollVertically(-1)) { + return false; } - return false; + mSwipeDetector.onTouchEvent(ev); + return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll(); } @Override public boolean onTouchEvent(MotionEvent ev) { - if (canScrollVertically(1) || canScrollVertically(-1)) { - return super.onTouchEvent(ev); + if (!canScrollVertically(1) && !canScrollVertically(-1)) { + return false; } - 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) { @@ -98,4 +118,81 @@ public class QSScrollLayout extends NestedScrollView { 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 3b9e7bcfb9b4..65135ab142d7 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 @@ public class TileLayout extends ViewGroup implements QSTileLayout { 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 ea6e174d786e..3597929229e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -21,6 +21,7 @@ import android.app.ActivityManager; import android.content.Intent; import android.provider.Settings; import android.service.quicksettings.Tile; +import android.support.annotation.StringRes; import android.widget.Switch; import com.android.internal.app.ColorDisplayController; @@ -30,6 +31,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.tileimpl.QSTileImpl; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class NightDisplayTile extends QSTileImpl<BooleanState> @@ -39,7 +41,9 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the * nearest hour and add on the AM/PM indicator. */ - private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h a"; + private static final String HOUR_MINUTE_DATE_TIME_PATTERN = "h a"; + private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h:m a"; + private ColorDisplayController mController; private boolean mIsListening; @@ -110,17 +114,26 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> case ColorDisplayController.AUTO_MODE_CUSTOM: // User-specified time, approximated to the nearest hour. - return isNightLightActivated - ? mContext.getString( - R.string.quick_settings_night_secondary_label_until, - mController.getCustomEndTime().format( - DateTimeFormatter.ofPattern( - APPROXIMATE_HOUR_DATE_TIME_PATTERN))) - : mContext.getString( - R.string.quick_settings_night_secondary_label_on_at, - mController.getCustomStartTime().format( - DateTimeFormatter.ofPattern( - APPROXIMATE_HOUR_DATE_TIME_PATTERN))); + final @StringRes int toggleTimeStringRes; + final LocalTime toggleTime; + final DateTimeFormatter toggleTimeFormat; + + if (isNightLightActivated) { + toggleTime = mController.getCustomEndTime(); + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_until; + } else { + toggleTime = mController.getCustomStartTime(); + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at; + } + + // Choose between just showing the hour or also showing the minutes (based on the + // user-selected toggle time). This helps reduce how much space the label takes. + toggleTimeFormat = DateTimeFormatter.ofPattern( + toggleTime.getMinute() == 0 + ? HOUR_MINUTE_DATE_TIME_PATTERN + : APPROXIMATE_HOUR_DATE_TIME_PATTERN); + + return mContext.getString(toggleTimeStringRes, toggleTime.format(toggleTimeFormat)); default: // No secondary label when auto mode is disabled. diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java new file mode 100644 index 000000000000..046488679725 --- /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 000000000000..252205201e5d --- /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 index b2472bf73874..0ff8b085f035 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java @@ -16,13 +16,11 @@ package com.android.systemui.recents; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import android.annotation.TargetApi; import android.app.ActivityManager; -import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -31,6 +29,8 @@ import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.RippleDrawable; import android.os.Build; +import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -41,6 +41,7 @@ import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; +import com.android.systemui.OverviewProxyService; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; @@ -50,9 +51,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; * Shows onboarding for the new recents interaction in P (codenamed quickstep). */ @TargetApi(Build.VERSION_CODES.P) -public class SwipeUpOnboarding { +public class RecentsOnboarding { - private static final String TAG = "SwipeUpOnboarding"; + private static final String TAG = "RecentsOnboarding"; private static final boolean RESET_PREFS_FOR_DEBUG = false; private static final long SHOW_DELAY_MS = 500; private static final long SHOW_HIDE_DURATION_MS = 300; @@ -61,6 +62,7 @@ public class SwipeUpOnboarding { private final Context mContext; private final WindowManager mWindowManager; + private final OverviewProxyService mOverviewProxyService; private final View mLayout; private final TextView mTextView; private final ImageView mDismissView; @@ -113,11 +115,12 @@ public class SwipeUpOnboarding { } }; - public SwipeUpOnboarding(Context context) { + public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) { mContext = context; + mOverviewProxyService = overviewProxyService; final Resources res = context.getResources(); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null); + mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null); mTextView = mLayout.findViewById(R.id.onboarding_text); mDismissView = mLayout.findViewById(R.id.dismiss); mDarkBackgroundColor = res.getColor(android.R.color.background_dark); @@ -135,25 +138,25 @@ public class SwipeUpOnboarding { mDismissView.setOnClickListener(v -> hide(true)); if (RESET_PREFS_FOR_DEBUG) { - Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0); } } public void onConnectedToLauncher() { - boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext, - Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); - if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) { + boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) { ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener); mTaskListenerRegistered = true; } } public void onRecentsAnimationStarted() { - boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext, - Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); - if (!alreadyLearnedSwipeUpForRecents) { - Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true); + boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false); + if (!alreadySeenRecentsOnboarding) { + Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true); onDisconnectedFromLauncher(); } } @@ -173,6 +176,12 @@ public class SwipeUpOnboarding { } public void show() { + CharSequence onboardingText = mOverviewProxyService.getOnboardingText(); + if (TextUtils.isEmpty(onboardingText)) { + Log.w(TAG, "Unable to get onboarding text"); + return; + } + mTextView.setText(onboardingText); // Only show in portrait. int orientation = mContext.getResources().getConfiguration().orientation; if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) { @@ -239,7 +248,7 @@ public class SwipeUpOnboarding { flags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; - lp.setTitle("SwipeUpOnboarding"); + lp.setTitle("RecentsOnboarding"); lp.gravity = Gravity.BOTTOM; return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 11bdf6b3c72e..fa177f2bb26c 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 @@ public class CommandQueue extends IStatusBar.Stub { 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 @@ public class CommandQueue extends IStatusBar.Stub { } @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 @@ public class CommandQueue extends IStatusBar.Stub { 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 d6beb7fb2699..ab89a5287cdb 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 @@ public class NotificationBackgroundView extends View { (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 3ebeb4d45c26..3dfb9130af2e 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 @@ public class CarStatusBar extends StatusBar implements // 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 8227b7797706..d3a325d3b91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java @@ -21,6 +21,8 @@ import android.util.Pools; import android.view.View; import android.widget.ImageView; +import com.android.internal.widget.MessagingImageMessage; +import com.android.internal.widget.MessagingMessage; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -117,13 +119,15 @@ public class ImageTransformState extends TransformState { @Override protected boolean transformScale(TransformState otherState) { - return true; + return sameAs(otherState); } @Override public void recycle() { super.recycle(); - sInstancePool.release(this); + if (getClass() == ImageTransformState.class) { + sInstancePool.release(this); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java new file mode 100644 index 000000000000..b97995dd5f93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification; + +import android.util.Pools; +import android.view.View; + +import com.android.internal.widget.MessagingImageMessage; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.statusbar.ViewTransformationHelper; + +/** + * A transform state of a image view. +*/ +public class MessagingImageTransformState extends ImageTransformState { + private static Pools.SimplePool<MessagingImageTransformState> sInstancePool + = new Pools.SimplePool<>(40); + private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width; + private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height; + private MessagingImageMessage mImageMessage; + + @Override + public void initFrom(View view, TransformInfo transformInfo) { + super.initFrom(view, transformInfo); + mImageMessage = (MessagingImageMessage) view; + } + + @Override + protected boolean sameAs(TransformState otherState) { + if (super.sameAs(otherState)) { + return true; + } + if (otherState instanceof MessagingImageTransformState) { + MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState; + return mImageMessage.sameAs(otherMessage.mImageMessage); + } + return false; + } + + public static MessagingImageTransformState obtain() { + MessagingImageTransformState instance = sInstancePool.acquire(); + if (instance != null) { + return instance; + } + return new MessagingImageTransformState(); + } + + @Override + protected boolean transformScale(TransformState otherState) { + return false; + } + + @Override + protected void transformViewFrom(TransformState otherState, int transformationFlags, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + super.transformViewFrom(otherState, transformationFlags, customTransformation, + transformationAmount); + float interpolatedValue = mDefaultInterpolator.getInterpolation( + transformationAmount); + if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) { + MessagingImageMessage otherMessage + = ((MessagingImageTransformState) otherState).mImageMessage; + if (transformationAmount == 0.0f) { + setStartActualWidth(otherMessage.getActualWidth()); + setStartActualHeight(otherMessage.getActualHeight()); + } + float startActualWidth = getStartActualWidth(); + mImageMessage.setActualWidth( + (int) NotificationUtils.interpolate(startActualWidth, + mImageMessage.getStaticWidth(), + interpolatedValue)); + float startActualHeight = getStartActualHeight(); + mImageMessage.setActualHeight( + (int) NotificationUtils.interpolate(startActualHeight, + mImageMessage.getHeight(), + interpolatedValue)); + } + } + + public int getStartActualWidth() { + Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH); + return tag == null ? -1 : (int) tag; + } + + public void setStartActualWidth(int actualWidth) { + mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth); + } + + public int getStartActualHeight() { + Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT); + return tag == null ? -1 : (int) tag; + } + + public void setStartActualHeight(int actualWidth) { + mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth); + } + + @Override + public void recycle() { + super.recycle(); + if (getClass() == MessagingImageTransformState.class) { + sInstancePool.release(this); + } + } + + @Override + protected void resetTransformedView() { + super.resetTransformedView(); + mImageMessage.setActualWidth(mImageMessage.getStaticWidth()); + mImageMessage.setActualHeight(mImageMessage.getHeight()); + } + + @Override + protected void reset() { + super.reset(); + mImageMessage = null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index 113118a1c8c5..314a31d336fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -22,6 +22,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.widget.MessagingGroup; +import com.android.internal.widget.MessagingImageMessage; import com.android.internal.widget.MessagingLayout; import com.android.internal.widget.MessagingLinearLayout; import com.android.internal.widget.MessagingMessage; @@ -30,6 +31,7 @@ import com.android.systemui.Interpolators; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * A transform state of the action list @@ -156,6 +158,7 @@ public class MessagingLayoutTransformState extends TransformState { } appear(ownGroup.getAvatar(), transformationAmount); appear(ownGroup.getSenderView(), transformationAmount); + appear(ownGroup.getIsolatedMessage(), transformationAmount); setClippingDeactivated(ownGroup.getSenderView(), true); setClippingDeactivated(ownGroup.getAvatar(), true); } @@ -187,12 +190,13 @@ public class MessagingLayoutTransformState extends TransformState { } disappear(ownGroup.getAvatar(), transformationAmount); disappear(ownGroup.getSenderView(), transformationAmount); + disappear(ownGroup.getIsolatedMessage(), transformationAmount); setClippingDeactivated(ownGroup.getSenderView(), true); setClippingDeactivated(ownGroup.getAvatar(), true); } private void appear(View child, float transformationAmount) { - if (child.getVisibility() == View.GONE) { + if (child == null || child.getVisibility() == View.GONE) { return; } TransformState ownState = TransformState.createFrom(child, mTransformInfo); @@ -201,7 +205,7 @@ public class MessagingLayoutTransformState extends TransformState { } private void disappear(View child, float transformationAmount) { - if (child.getVisibility() == View.GONE) { + if (child == null || child.getVisibility() == View.GONE) { return; } TransformState ownState = TransformState.createFrom(child, mTransformInfo); @@ -224,22 +228,24 @@ public class MessagingLayoutTransformState extends TransformState { private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup, float transformationAmount, boolean to) { + boolean useLinearTransformation = + otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), - true /* sameAsAny */); + true /* sameAsAny */, useLinearTransformation); transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), - true /* sameAsAny */); - MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); - MessagingLinearLayout otherMessages = otherGroup.getMessageContainer(); + true /* sameAsAny */, useLinearTransformation); + List<MessagingMessage> ownMessages = ownGroup.getMessages(); + List<MessagingMessage> otherMessages = otherGroup.getMessages(); float previousTranslation = 0; - for (int i = 0; i < ownMessages.getChildCount(); i++) { - View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i); + for (int i = 0; i < ownMessages.size(); i++) { + View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); if (isGone(child)) { continue; } - int otherIndex = otherMessages.getChildCount() - 1 - i; + int otherIndex = otherMessages.size() - 1 - i; View otherChild = null; if (otherIndex >= 0) { - otherChild = otherMessages.getChildAt(otherIndex); + otherChild = otherMessages.get(otherIndex).getView(); if (isGone(otherChild)) { otherChild = null; } @@ -252,7 +258,12 @@ public class MessagingLayoutTransformState extends TransformState { transformationAmount = 1.0f - transformationAmount; } } - transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */); + transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */ + useLinearTransformation); + if (transformationAmount == 0.0f + && otherGroup.getIsolatedMessage() == otherChild) { + ownGroup.setTransformingImages(true); + } if (otherChild == null) { child.setTranslationY(previousTranslation); setClippingDeactivated(child, true); @@ -264,12 +275,13 @@ public class MessagingLayoutTransformState extends TransformState { previousTranslation = child.getTranslationY(); } } + ownGroup.updateClipRect(); } private void transformView(float transformationAmount, boolean to, View ownView, - View otherView, boolean sameAsAny) { + View otherView, boolean sameAsAny, boolean useLinearTransformation) { TransformState ownState = TransformState.createFrom(ownView, mTransformInfo); - if (!mTransformInfo.isAnimating()) { + if (useLinearTransformation) { ownState.setDefaultInterpolator(Interpolators.LINEAR); } ownState.setIsSameAsAnyView(sameAsAny); @@ -339,11 +351,15 @@ public class MessagingLayoutTransformState extends TransformState { if (!isGone(ownGroup)) { MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); for (int j = 0; j < ownMessages.getChildCount(); j++) { - MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j); + View child = ownMessages.getChildAt(j); setVisible(child, visible, force); } setVisible(ownGroup.getAvatar(), visible, force); setVisible(ownGroup.getSenderView(), visible, force); + MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + setVisible(isolatedMessage, visible, force); + } } } } @@ -375,11 +391,17 @@ public class MessagingLayoutTransformState extends TransformState { } resetTransformedView(ownGroup.getAvatar()); resetTransformedView(ownGroup.getSenderView()); + MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + resetTransformedView(isolatedMessage); + } setClippingDeactivated(ownGroup.getAvatar(), false); setClippingDeactivated(ownGroup.getSenderView(), false); ownGroup.setTranslationY(0); ownGroup.getMessageContainer().setTranslationY(0); } + ownGroup.setTransformingImages(false); + ownGroup.updateClipRect(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 918b6edc0c30..fc8ceb6620a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -26,6 +26,7 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.internal.widget.MessagingImageMessage; import com.android.internal.widget.MessagingPropertyAnimator; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Interpolators; @@ -80,7 +81,7 @@ public class TransformState { private boolean mSameAsAny; private float mTransformationEndY = UNDEFINED; private float mTransformationEndX = UNDEFINED; - private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN; + protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN; public void initFrom(View view, TransformInfo transformInfo) { mTransformedView = view; @@ -131,7 +132,7 @@ public class TransformState { transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount); } - private void transformViewFrom(TransformState otherState, int transformationFlags, + protected void transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount) { final View transformedView = mTransformedView; @@ -449,6 +450,11 @@ public class TransformState { result.initFrom(view, transformInfo); return result; } + if (view instanceof MessagingImageMessage) { + MessagingImageTransformState result = MessagingImageTransformState.obtain(); + result.initFrom(view, transformInfo); + return result; + } if (view instanceof ImageView) { ImageTransformState result = ImageTransformState.obtain(); result.initFrom(view, transformInfo); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java new file mode 100644 index 000000000000..aba5cdf0ca2b --- /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 c85571c1895d..2bfdefe39017 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 android.view.ViewConfiguration; 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 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private NotificationStackScrollLayout mStackScroller; private int mTrackingPointer; private float mTouchSlop; @@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { 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 0d36efda9ece..8d14db78fe29 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 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture 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 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture 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 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture } 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 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture 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 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture 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 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture return DRAG_MODE_RECENTS; } - public boolean onTouchEvent(MotionEvent event) { - // The same down event was just sent on intercept and therefore can be ignored here - boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN - && mOverviewEventSender.getProxy() != null; - boolean result = mStatusBar.isPresenterFullyCollapsed() - && (mQuickScrubController.onTouchEvent(event) - || ignoreProxyDownEvent - || proxyMotionEvents(event)); - if (mDockWindowEnabled) { - result |= handleDockWindowEvent(event); - } - return result; - } - @Override public void onTuningChanged(String key, String newValue) { switch (key) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index c37dd55cc6ff..af0afbdd6782 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -50,14 +50,13 @@ import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.DockedStackExistsListener; import com.android.systemui.OverviewProxyService; -import com.android.systemui.OverviewProxyService.OverviewProxyListener; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.phone.NavGesture; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; -import com.android.systemui.recents.SwipeUpOnboarding; +import com.android.systemui.recents.RecentsOnboarding; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.policy.DeadZone; import com.android.systemui.statusbar.policy.KeyButtonDrawable; @@ -104,7 +103,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav 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 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private NavigationBarInflaterView mNavigationInflaterView; private RecentsComponent mRecentsComponent; private Divider mDivider; - private SwipeUpOnboarding mSwipeUpOnboarding; + private RecentsOnboarding mRecentsOnboarding; private NotificationPanelView mPanelView; private class NavTransitionListener implements TransitionListener { @@ -236,7 +234,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav new ButtonDispatcher(R.id.rotate_suggestion)); mOverviewProxyService = Dependency.get(OverviewProxyService.class); - mSwipeUpOnboarding = new SwipeUpOnboarding(context); + mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService); } public BarTransitions getBarTransitions() { @@ -264,9 +262,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } public void setRecentsAnimationStarted(boolean started) { - mRecentsAnimationStarted = started; - if (mSwipeUpOnboarding != null) { - mSwipeUpOnboarding.onRecentsAnimationStarted(); + if (mRecentsOnboarding != null) { + mRecentsOnboarding.onRecentsAnimationStarted(); } } @@ -277,30 +274,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } @Override - public boolean onTouchEvent(MotionEvent event) { - if (mGestureHelper.onTouchEvent(event)) { - return true; - } - return mRecentsAnimationStarted || super.onTouchEvent(event); + public boolean onInterceptTouchEvent(MotionEvent event) { + return mGestureHelper.onInterceptTouchEvent(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"); - } - } + public boolean onTouchEvent(MotionEvent event) { + if (mGestureHelper.onTouchEvent(event)) { + return true; } - return mGestureHelper.onInterceptTouchEvent(event) || mRecentsAnimationStarted; + return super.onTouchEvent(event); } public void abortCurrentGesture() { @@ -674,8 +657,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper != null) { mGestureHelper.onDarkIntensityChange(intensity); } - if (mSwipeUpOnboarding != null) { - mSwipeUpOnboarding.setContentDarkIntensity(intensity); + if (mRecentsOnboarding != null) { + mRecentsOnboarding.setContentDarkIntensity(intensity); } } @@ -793,7 +776,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateTaskSwitchHelper(); updateIcons(getContext(), mConfiguration, newConfig); updateRecentsIcon(); - mSwipeUpOnboarding.onConfigurationChanged(newConfig); + mRecentsOnboarding.onConfigurationChanged(newConfig); if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) { // If car mode or density changes, we need to reset the icons. @@ -897,9 +880,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { if (connectedToOverviewProxy) { - mSwipeUpOnboarding.onConnectedToLauncher(); + mRecentsOnboarding.onConnectedToLauncher(); } else { - mSwipeUpOnboarding.onDisconnectedFromLauncher(); + mRecentsOnboarding.onDisconnectedFromLauncher(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index cd2e77ae2591..52d005cb152a 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.NotificationData; 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 @@ public class NotificationPanelView extends PanelView implements private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed(); if (mPanelExpanded != isExpanded) { - mHeadsUpManager.setIsExpanded(isExpanded); + mHeadsUpManager.setIsPanelExpanded(isExpanded); mStatusBar.setPanelExpanded(isExpanded); mPanelExpanded = isExpanded; } @@ -2338,7 +2336,7 @@ public class NotificationPanelView extends PanelView implements } @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 class NotificationPanelView extends PanelView implements } } - 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 2b7e4747a837..6daabede7f32 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.classifier.FalsingManager; 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 @@ public abstract class PanelView extends FrameLayout { } protected StatusBar mStatusBar; - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private float mPeekHeight; private float mHintDistance; @@ -1252,7 +1252,7 @@ public abstract class PanelView extends FrameLayout { */ 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 001a1a2d7292..dc0835e4371a 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 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene 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 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene private boolean mDraggingActive; private boolean mQuickScrubActive; + private boolean mAllowQuickSwitch; private float mDownOffset; private float mTranslation; private int mTouchDownX; @@ -95,7 +96,6 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene 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 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene 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 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene } catch (RemoteException e) { Log.e(TAG, "Failed to send start of quick switch.", e); } + return true; } - return true; + return false; } }; 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 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene mNavigationBarView = navigationBarView; } + /** + * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying + * the event to the overview service. + */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); @@ -197,7 +201,26 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene 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 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene homeButton.setDelayTouchFeedback(false); mTouchDownX = mTouchDownY = -1; } + mAllowQuickSwitch = true; break; } case MotionEvent.ACTION_MOVE: { @@ -240,8 +264,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene offset = pos - mTrackRect.left; trackSize = mTrackRect.width(); } - // Do not start scrubbing when dragging in the perpendicular direction - if (!mDraggingActive && exceededPerpendicularTouchSlop) { + // Do not start scrubbing when dragging in the perpendicular direction if we + // haven't already started quickscrub + if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) { mHandler.removeCallbacksAndMessages(null); return false; } @@ -341,14 +366,6 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene } @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - endQuickScrub(); - } - return false; - } - - @Override public void setBarState(boolean isVertical, boolean isRTL) { mIsVertical = isVertical; mIsRTL = isRTL; @@ -403,6 +420,11 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene 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 44e0c257ef24..2b16e740d0ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.os.Trace; import android.util.Log; import android.util.MathUtils; +import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -111,7 +112,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; static final int TAG_KEY_ANIM = R.id.scrim; - static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking; private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target; private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; @@ -166,6 +166,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mScreenBlankingCallbackCalled; private Callback mCallback; private boolean mWallpaperSupportsAmbientMode; + private Choreographer.FrameCallback mPendingFrameCallback; private final WakeLock mWakeLock; private boolean mWakeLockHeld; @@ -248,6 +249,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mCurrentInFrontAlpha = state.getFrontAlpha(); mCurrentBehindAlpha = state.getBehindAlpha(); + if (mPendingFrameCallback != null) { + Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback); + mPendingFrameCallback = null; + } + // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary // to do the same when you're just showing the brightness mirror. mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; @@ -276,13 +282,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mWallpaperVisibilityTimedOut = false; } - if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { - scheduleUpdate(); - } else { + if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { // In case the user isn't unlocked, make sure to delay a bit because the system is hosed // with too many things at this case, in order to not skip the initial frames. mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; + } else if (!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) { + // Execute first frame immediately when display was completely off. + // Scheduling a frame isn't enough because the system may aggressively enter doze, + // delaying callbacks or never triggering them until the power button is pressed. + onPreDraw(); + } else { + scheduleUpdate(); } } @@ -687,11 +698,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } } - final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null; - if (mBlankScreen || blankingInProgress) { - if (!blankingInProgress) { - blankDisplay(); - } + if (mPendingFrameCallback != null) { + // Display is off and we're waiting. + return; + } else if (mBlankScreen) { + // Need to blank the display before continuing. + blankDisplay(); return; } else if (!mScreenBlankingCallbackCalled) { // Not blanking the screen. Letting the callback know that we're ready @@ -745,45 +757,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void blankDisplay() { - final float initialAlpha = mScrimInFront.getViewAlpha(); - final int initialTint = mScrimInFront.getTint(); - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - anim.addUpdateListener(animation -> { - final float amount = (float) animation.getAnimatedValue(); - float animAlpha = MathUtils.lerp(initialAlpha, 1, amount); - int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount); - updateScrimColor(mScrimInFront, animAlpha, animTint); - dispatchScrimsVisible(); - }); - anim.setInterpolator(getInterpolator()); - anim.setDuration(mDozeParameters.getPulseInDuration()); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCallback != null) { - mCallback.onDisplayBlanked(); - mScreenBlankingCallbackCalled = true; - } - Runnable blankingCallback = () -> { - mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null); - mBlankScreen = false; - // Try again. - updateScrims(); - }; - - // Setting power states can happen after we push out the frame. Make sure we - // stay fully opaque until the power state request reaches the lower levels. - getHandler().postDelayed(blankingCallback, 100); + updateScrimColor(mScrimInFront, 1, Color.BLACK); + // Notify callback that the screen is completely black and we're + // ready to change the display power mode + mPendingFrameCallback = frameTimeNanos -> { + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; } - }); - anim.start(); - mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim); - // Finish animation if we're already at its final state - if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) { - anim.end(); - } + Runnable blankingCallback = () -> { + mPendingFrameCallback = null; + mBlankScreen = false; + // Try again. + updateScrims(); + }; + + // Setting power states can happen after we push out the frame. Make sure we + // stay fully opaque until the power state request reaches the lower levels. + getHandler().postDelayed(blankingCallback, 100); + }; + doOnTheNextFrame(mPendingFrameCallback); + } + + @VisibleForTesting + protected void doOnTheNextFrame(Choreographer.FrameCallback callback) { + Choreographer.getInstance().postFrameCallback(callback); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 314d6aa2bf57..381e4af31853 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -48,7 +48,6 @@ public enum ScrimState { // set our scrim to black in this frame to avoid flickering and // fade it out afterwards. mBlankScreen = true; - updateScrimColor(mScrimInFront, 1, Color.BLACK); } } else { mAnimationDuration = ScrimController.ANIMATION_DURATION; @@ -86,9 +85,6 @@ public enum ScrimState { AOD(3) { @Override public void prepare(ScrimState previousState) { - if (previousState == ScrimState.PULSING && !mCanControlScreenOff) { - updateScrimColor(mScrimInFront, 1, Color.BLACK); - } final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); final boolean wasPulsing = previousState == ScrimState.PULSING; mBlankScreen = wasPulsing && !mCanControlScreenOff; @@ -115,9 +111,6 @@ public enum ScrimState { && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f; mCurrentBehindTint = Color.BLACK; mBlankScreen = mDisplayRequiresBlanking; - if (mDisplayRequiresBlanking) { - updateScrimColor(mScrimInFront, 1, Color.BLACK); - } } }, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1bf719ae68af..8c112a623e18 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.KeyguardUpdateMonitorCallback; 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.SystemUI; 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.stackdivider.WindowManagerProxy; 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.StatusBarState; 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; 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 @@ public class StatusBar extends SystemUI implements DemoMode, .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 @@ public class StatusBar extends SystemUI implements DemoMode, @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 class StatusBar extends SystemUI implements DemoMode, } 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void showChargingAnimation(int batteryLevel) { - if (mDozing) { - // ambient - } else if (mKeyguardManager.isKeyguardLocked()) { - // lockscreen + public void showWirelessChargingAnimation(int batteryLevel) { + if (mDozing || mKeyguardManager.isKeyguardLocked()) { + // on ambient or lockscreen, hide notification panel + WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, + 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).show(); + batteryLevel, null).show(); } } @@ -3882,6 +3895,7 @@ public class StatusBar extends SystemUI implements DemoMode, private void instantCollapseNotificationPanel() { mNotificationPanel.instantCollapse(); + runPostCollapseRunnables(); } @Override @@ -4658,24 +4672,22 @@ public class StatusBar extends SystemUI implements DemoMode, @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 +4835,7 @@ public class StatusBar extends SystemUI implements DemoMode, // for heads up notifications - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private AboveShelfObserver mAboveShelfObserver; @@ -4926,7 +4938,7 @@ public class StatusBar extends SystemUI implements DemoMode, // 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 +5057,8 @@ public class StatusBar extends SystemUI implements DemoMode, } 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 53dfb244c776..040d7ec32ecc 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; - 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>() { + protected final Context mContext; - private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); + protected int mHeadsUpNotificationDecay; + protected int mMinimumDisplayTime; + protected int mTouchAcceptanceDelay; + protected int mSnoozeLengthMs; + protected boolean mHasPinnedNotification; + protected int mUser; - @Override - public HeadsUpEntry acquire() { - if (!mPoolObjects.isEmpty()) { - return mPoolObjects.pop(); - } - return new HeadsUpEntry(); - } + private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private final ArrayMap<String, Long> mSnoozedPackages; - @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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL }; 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); - } - - 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; + settingsObserver); } - 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL /** * 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL // 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL 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; + } + + /** + * Returns the stream of all current Heads Up Notifications. + */ + @NonNull + public Stream<NotificationData.Entry> getAllEntries() { + return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry); + } + + /** + * 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; } - public Collection<HeadsUpEntry> getAllEntries() { - return mHeadsUpEntries.values(); + /** + * Returns if any heads up notification is available or not. + */ + public boolean hasHeadsUpNotifications() { + return !mHeadsUpEntries.isEmpty(); } - public HeadsUpEntry getTopEntry() { + @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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } + /** + * Returns if there are any pinned Heads Up Notifications or not. + */ public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } @@ -464,14 +388,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * 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 class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * 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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } @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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL : -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 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL 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 000000000000..1e3c123cfbc6 --- /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 077c6c38c516..ea449c2fe3df 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.metrics.LogMaker; 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 android.widget.ImageView; 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 @@ public class KeyButtonView extends ImageView implements ButtonInterface { 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 @@ public class KeyButtonView extends ImageView implements ButtonInterface { 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 class KeyButtonView extends ImageView implements ButtonInterface { } 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 @@ public class KeyButtonView extends ImageView implements ButtonInterface { 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 @@ public class KeyButtonView extends ImageView implements ButtonInterface { 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 424858a86e58..d7a810eca02e 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 @@ public class AmbientState { 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 class AmbientState { } 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 c114a6f5a6d9..1b55a5b0325f 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.StatusBarState; 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup } }; 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 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - if (mPulsing != null) { + if (mPulsing) { mTopPadding = mClockBottom; } else { mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; @@ -920,6 +921,27 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @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 @@ public class NotificationStackScrollLayout extends ViewGroup int minNotificationsForShelf = 1; if (mTrackingHeadsUp || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { - appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); + appearPosition = getTopHeadsUpPinnedHeight(); minNotificationsForShelf = 2; } else { appearPosition = 0; @@ -1198,9 +1220,9 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } private void updateScrollability() { @@ -2753,7 +2775,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private boolean isClickedHeadsUp(View child) { - return HeadsUpManager.isClickedHeadsUpNotification(child); + return HeadsUpUtil.isClickedHeadsUpNotification(child); } /** @@ -4258,7 +4280,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationFinishedRunnables.add(runnable); } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); } @@ -4326,8 +4348,8 @@ public class NotificationStackScrollLayout extends ViewGroup 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 @@ public class NotificationStackScrollLayout extends ViewGroup 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 682b8493e913..04a7bd79c6ca 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.R; 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 @@ public class ViewState { 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 46c43c206f8a..6ed07f8d2c37 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java @@ -49,6 +49,8 @@ import android.util.Pair; import android.view.Window; import android.view.WindowManager; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.Dependency; @@ -202,6 +204,7 @@ public class OutputChooserDialog extends Dialog @Override public void show() { super.show(); + Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER); mHardwareLayout.setTranslationX(getAnimTranslation()); mHardwareLayout.setAlpha(0); mHardwareLayout.animate() @@ -215,6 +218,7 @@ public class OutputChooserDialog extends Dialog @Override public void dismiss() { + Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER); mHardwareLayout.setTranslationX(0); mHardwareLayout.setAlpha(1); mHardwareLayout.animate() @@ -237,11 +241,15 @@ public class OutputChooserDialog extends Dialog if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) { final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT); mBluetoothController.connect(device); } } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) { final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag; if (route.isEnabled()) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT); route.select(); } } @@ -252,8 +260,12 @@ public class OutputChooserDialog extends Dialog if (item == null || item.tag == null) return; if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) { final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT); mBluetoothController.disconnect(device); } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) { + Dependency.get(MetricsLogger.class).action( + MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT); mRouter.unselect(UNSELECT_REASON_DISCONNECTED); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index efa8386802e2..0203c43d3683 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -27,16 +27,15 @@ import android.os.Handler; import android.view.WindowManager.LayoutParams; import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionController.Extension; import com.android.systemui.tuner.TunerService; import java.io.FileDescriptor; @@ -53,8 +52,8 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb"; public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false; - public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true; - public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true; + public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false; + public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false; private final SystemUI mSysui; private final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index a131a618512f..c622677ceb7e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -396,13 +396,9 @@ public class VolumeDialogImpl implements VolumeDialog { if (hasVibrator) { mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); } else { - final boolean wasZero = ss.level == 0; - mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0); mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); } } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { - final boolean wasZero = ss.level == 0; - mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0); mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); } else { mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 936ff512b8cd..59a7da68dd31 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -65,7 +65,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \ android.test.runner \ telephony-common \ - android.car \ android.test.base \ 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 3e37cfe75e0f..bf6cc53aa5e9 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 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { 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 0a51e5a4e389..4b455baee56b 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 @@ public class PowerUITest extends SysuiTestCase { // 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 6e7477fbac38..f3c1171f650c 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.AboveShelfChangedListener; 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 class NotificationTestHelper { 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 000000000000..aa991cb71aac --- /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 6d2691c899f2..43e16dbeaeed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -38,6 +38,7 @@ import android.os.Looper; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.Choreographer; import android.view.View; import com.android.keyguard.KeyguardUpdateMonitor; @@ -374,7 +375,6 @@ public class ScrimControllerTest extends SysuiTestCase { onPreDraw(); // Force finish screen blanking. - endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK); mHandler.dispatchQueuedMessages(); // Force finish all animations. endAnimation(mScrimBehind, TAG_KEY_ANIM); @@ -401,6 +401,15 @@ public class ScrimControllerTest extends SysuiTestCase { protected WakeLock createWakeLock() { return mWakeLock; } + + /** + * Do not wait for a frame since we're in a test environment. + * @param callback What to execute. + */ + @Override + protected void doOnTheNextFrame(Choreographer.FrameCallback callback) { + callback.doFrame(0); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index bdf9b1f6da9e..31442af5a04c 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.NotificationViewHierarchyManager; 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 @@ public class StatusBarTest extends SysuiTestCase { @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 @@ public class StatusBarTest extends SysuiTestCase { 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 class StatusBarTest extends SysuiTestCase { 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 a10bebfd4f2d..e1b97bdadadc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -29,12 +29,14 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest +@Ignore public class LocationControllerImplTest extends SysuiTestCase { private LocationControllerImpl mLocationController; @@ -79,4 +81,4 @@ public class LocationControllerImplTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); } -}
\ No newline at end of file +} diff --git a/proto/Android.bp b/proto/Android.bp index 95f453c3e523..f3811bdd7d81 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -6,6 +6,8 @@ java_library_static { }, srcs: ["src/**/*.proto"], no_framework_libs: true, + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", target: { android: { jarjar_rules: "jarjar-rules.txt", diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 01714cf571b5..ae5e133fa02f 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5171,6 +5171,7 @@ message MetricsEvent { // An autofill service was bound using an unofficial(but still supported) permission. // Package: Package of the autofill service // OS: P + AUTOFILL_INVALID_PERMISSION = 1289; // OPEN: QS Alarm tile shown @@ -5185,6 +5186,33 @@ message MetricsEvent { // OS: P USB_DEVICE_DETAILS = 1291; + // OPEN: Settings > Accessibility > Vibration + // CATEGORY: SETTINGS + // OS: P + ACCESSIBILITY_VIBRATION = 1292; + + // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration + // CATEGORY: SETTINGS + // OS: P + ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293; + + // OPEN: Settings > Accessibility > Vibration > Touch vibration + // CATEGORY: SETTINGS + // OS: P + ACCESSIBILITY_VIBRATION_TOUCH = 1294; + + // OPEN: Volume panel > output chooser dialog + // OS: P + OUTPUT_CHOOSER = 1295; + + // Action: Volume panel > output chooser dialog > tap on device + // OS: P + ACTION_OUTPUT_CHOOSER_CONNECT = 1296; + + // Action: Volume panel > output chooser dialog > tap on X next to connected device + // OS: P + ACTION_OUTPUT_CHOOSER_DISCONNECT = 1297; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index b32be736533b..52d0e08e4e7f 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -24,8 +24,9 @@ #include <utils/misc.h> #include <inttypes.h> +#include <android-base/macros.h> #include <androidfw/Asset.h> -#include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include <androidfw/ResourceTypes.h> #include <android-base/macros.h> @@ -1664,18 +1665,22 @@ nFileA3DCreateFromAssetStream(JNIEnv *_env, jobject _this, jlong con, jlong nati static jlong nFileA3DCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path) { - AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr); + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr); if (mgr == nullptr) { return 0; } AutoJavaStringToUTF8 str(_env, _path); - Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - if (asset == nullptr) { - return 0; + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + if (asset == nullptr) { + return 0; + } } - jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset); + jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset.release()); return id; } @@ -1752,22 +1757,25 @@ static jlong nFontCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path, jfloat fontSize, jint dpi) { - AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr); + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr); if (mgr == nullptr) { return 0; } AutoJavaStringToUTF8 str(_env, _path); - Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - if (asset == nullptr) { - return 0; + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + if (asset == nullptr) { + return 0; + } } jlong id = (jlong)(uintptr_t)rsFontCreateFromMemory((RsContext)con, str.c_str(), str.length(), fontSize, dpi, asset->getBuffer(false), asset->getLength()); - delete asset; return id; } diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index a70b88e87ae4..c36bb6d2e096 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 @@ class MagnificationController implements Handler.Callback { } } + @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 72c3c94baa70..d5a722bba723 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 @@ public final class AutofillManagerService extends SystemService { * * @return service instance. */ + @GuardedBy("mLock") @NonNull AutofillManagerServiceImpl getServiceForUserLocked(int userId) { final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), @@ -325,6 +326,7 @@ public final class AutofillManagerService extends SystemService { * * @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 @@ public final class AutofillManagerService extends SystemService { /** * 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 @@ public final class AutofillManagerService extends SystemService { /** * Updates a cached service for a given user. */ + @GuardedBy("mLock") private void updateCachedServiceLocked(int userId) { updateCachedServiceLocked(userId, mDisabledUsers.get(userId)); } @@ -492,6 +496,7 @@ public final class AutofillManagerService extends SystemService { /** * 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 989a7b5619d2..2dcc6da0be32 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 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") private int getServiceUidLocked() { if (mInfo == null) { Slog.w(TAG, "getServiceUidLocked(): no mInfo"); @@ -248,6 +249,7 @@ final class AutofillManagerServiceImpl { mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); } + @GuardedBy("mLock") void updateLocked(boolean disabled) { final boolean wasEnabled = isEnabledLocked(); if (sVerbose) { @@ -300,6 +302,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") boolean addClientLocked(IAutoFillManagerClient client) { if (mClients == null) { mClients = new RemoteCallbackList<>(); @@ -308,12 +311,14 @@ final class AutofillManagerServiceImpl { 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 @@ final class AutofillManagerServiceImpl { } } + @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 @@ final class AutofillManagerServiceImpl { /** * 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 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") void finishSessionLocked(int sessionId, int uid) { if (!isEnabledLocked()) { return; @@ -423,6 +431,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") void cancelSessionLocked(int sessionId, int uid) { if (!isEnabledLocked()) { return; @@ -436,6 +445,7 @@ final class AutofillManagerServiceImpl { session.removeSelfLocked(); } + @GuardedBy("mLock") void disableOwnedAutofillServicesLocked(int uid) { Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo); if (mInfo == null) return; @@ -468,6 +478,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull ComponentName componentName, int flags) { @@ -546,6 +557,7 @@ final class AutofillManagerServiceImpl { /** * 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 @@ final class AutofillManagerServiceImpl { return false; } + @GuardedBy("mLock") void removeSessionLocked(int sessionId) { mSessions.remove(sessionId); } @@ -603,6 +616,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") void destroyLocked() { if (sVerbose) Slog.v(TAG, "destroyLocked()"); @@ -655,6 +669,7 @@ final class AutofillManagerServiceImpl { } } + @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 @@ final class AutofillManagerServiceImpl { /** * 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 @@ final class AutofillManagerServiceImpl { 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 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") private boolean isCalledByServiceLocked(String methodName, int callingUid) { if (getServiceUidLocked() != callingUid) { Slog.w(TAG, methodName + "() called by UID " + callingUid @@ -856,6 +874,7 @@ final class AutofillManagerServiceImpl { return true; } + @GuardedBy("mLock") void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; @@ -965,6 +984,7 @@ final class AutofillManagerServiceImpl { mFieldClassificationStrategy.dump(prefix2, pw); } + @GuardedBy("mLock") void destroySessionsLocked() { if (mSessions.size() == 0) { mUi.destroyAll(null, null, false); @@ -976,6 +996,7 @@ final class AutofillManagerServiceImpl { } // 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--) { @@ -987,6 +1008,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") void listSessionsLocked(ArrayList<String> output) { final int numSessions = mSessions.size(); for (int i = 0; i < numSessions; i++) { @@ -995,6 +1017,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") boolean isCompatibilityModeRequestedLocked(@NonNull String packageName, long versionCode) { if (mInfo == null || !mInfo.isCompatibilityModeRequested(packageName, versionCode)) { @@ -1060,6 +1083,7 @@ final class AutofillManagerServiceImpl { } } + @GuardedBy("mLock") private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) { final int sessionCount = mSessions.size(); for (int i = 0; i < sessionCount; i++) { @@ -1071,6 +1095,7 @@ final class AutofillManagerServiceImpl { return true; } + @GuardedBy("mLock") boolean isEnabledLocked() { return mSetupComplete && mInfo != null && !mDisabled; } @@ -1123,6 +1148,7 @@ final class AutofillManagerServiceImpl { /** * 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 aea9ad0e33da..fe6d4c41c09d 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 @@ final class RemoteFillService implements DeathRecipient { 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 18f49ec5c454..4a247049d395 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * <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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Cancels the last request sent to the {@link #mRemoteFillService}. */ + @GuardedBy("mLock") private void cancelCurrentRequestLocked() { final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); @@ -430,6 +434,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * @return The activity token */ + @GuardedBy("mLock") @NonNull IBinder getActivityTokenLocked() { return mActivityToken; } @@ -663,6 +669,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * @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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }); } + @GuardedBy("mLock") void setAuthenticationResultLocked(Bundle data, int authenticationId) { if (mDestroyed) { Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " @@ -882,6 +890,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") void setHasCallbackLocked(boolean hasIt) { if (mDestroyed) { Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " @@ -891,6 +900,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mHasCallback = hasIt; } + @GuardedBy("mLock") @Nullable private FillResponse getLastResponseLocked(@Nullable String logPrefix) { if (mContexts == null) { @@ -923,6 +933,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return response; } + @GuardedBy("mLock") @Nullable private SaveInfo getSaveInfoLocked() { final FillResponse response = getLastResponseLocked(null); @@ -941,6 +952,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }); } + @GuardedBy("mLock") private void logContextCommittedLocked() { final FillResponse lastResponse = getLastResponseLocked("logContextCommited()"); if (lastResponse == null) return; @@ -1241,6 +1253,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * @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: " @@ -1510,6 +1523,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Returns whether the session is currently showing the save UI */ + @GuardedBy("mLock") boolean isSavingLocked() { return mIsSaving; } @@ -1517,6 +1531,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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(); @@ -1539,6 +1554,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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(); @@ -1556,6 +1572,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Calls service when user requested save. */ + @GuardedBy("mLock") void callSaveLocked() { if (mDestroyed) { Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " @@ -1646,6 +1663,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @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) { @@ -1673,6 +1691,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * @return {@code true} iff a new partition should be started */ + @GuardedBy("mLock") private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { if (mResponses == null) { return true; @@ -1721,6 +1740,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return true; } + @GuardedBy("mLock") void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags) { if (mDestroyed) { @@ -1830,6 +1850,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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); @@ -1910,6 +1931,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") private void updateTrackedIdsLocked() { // Only track the views of the last response as only those are reported back to the // service, see #showSaveLocked @@ -1982,6 +2004,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") private void replaceResponseLocked(@NonNull FillResponse oldResponse, @NonNull FillResponse newResponse, @Nullable Bundle newClientState) { // Disassociate view states with the old response @@ -2005,6 +2028,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ -2042,6 +2066,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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) { @@ -2090,6 +2115,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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(); @@ -2110,6 +2136,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state, @Nullable AutofillValue value) { ViewState viewState = mViewStates.get(id); @@ -2171,6 +2198,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // 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(); @@ -2203,6 +2231,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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); @@ -2332,6 +2361,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * disabled it). * </ul> */ + @GuardedBy("mLock") RemoteFillService destroyLocked() { if (mDestroyed) { return null; @@ -2347,6 +2377,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * 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); @@ -2376,6 +2407,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * 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) { @@ -2404,6 +2436,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * 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()); } @@ -2411,10 +2444,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * 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 3b80f555d458..83367f3f83b4 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 @@ public class BackupManagerService implements BackupManagerServiceInterface { /** * 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 17c617c095d8..7ab5812fd31a 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -88,6 +88,7 @@ import java.util.TreeSet; 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; @@ -1011,7 +1012,7 @@ class AlarmManagerService extends SystemService { // 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; @@ -1507,24 +1508,19 @@ class AlarmManagerService extends SystemService { * Adjusts the alarm delivery time based on the current app standby bucket. * @param alarm The alarm to adjust * @return true if the alarm delivery time was updated. - * TODO: Reduce the number of calls to getAppStandbyBucket by batching the calls per - * {package, user} pairs */ private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) { - if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) { - return false; - } - // TODO: short term fix for b/72816079, remove after a proper fix is in place - if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { + if (isExemptFromAppStandby(alarm)) { return false; } if (mAppStandbyParole) { - if (alarm.whenElapsed > alarm.requestedWhenElapsed) { + if (alarm.whenElapsed > alarm.expectedWhenElapsed) { // We did defer this alarm earlier, restore original requirements - alarm.whenElapsed = alarm.requestedWhenElapsed; - alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed; + alarm.whenElapsed = alarm.expectedWhenElapsed; + alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed; + return true; } - return true; + return false; } final long oldWhenElapsed = alarm.whenElapsed; final long oldMaxWhenElapsed = alarm.maxWhenElapsed; @@ -1538,13 +1534,13 @@ class AlarmManagerService extends SystemService { 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.requestedWhenElapsed; - alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed; + alarm.whenElapsed = alarm.expectedWhenElapsed; + alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed; } } return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed); @@ -1728,7 +1724,7 @@ class AlarmManagerService extends SystemService { // 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; @@ -3000,10 +2996,11 @@ class AlarmManagerService extends SystemService { // 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; @@ -3053,7 +3050,7 @@ class AlarmManagerService extends SystemService { 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; @@ -3128,8 +3125,9 @@ class AlarmManagerService extends SystemService { 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; @@ -3143,10 +3141,10 @@ class AlarmManagerService extends SystemService { || _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; @@ -3205,8 +3203,10 @@ class AlarmManagerService extends SystemService { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); pw.print(prefix); pw.print("tag="); pw.println(statsTag); pw.print(prefix); pw.print("type="); pw.print(type); - pw.print(" requestedWhenElapsed="); TimeUtils.formatDuration( - 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="); @@ -3344,6 +3344,11 @@ class AlarmManagerService extends SystemService { } } + 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() @@ -3462,7 +3467,7 @@ class AlarmManagerService extends SystemService { new ArraySet<>(); for (int i = 0; i < triggerList.size(); i++) { final Alarm a = triggerList.get(i); - if (!UserHandle.isCore(a.creatorUid)) { + if (!isExemptFromAppStandby(a)) { triggerPackages.add(Pair.create( a.sourcePackage, UserHandle.getUserId(a.creatorUid))); } @@ -4056,6 +4061,7 @@ class AlarmManagerService extends SystemService { /** * 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 @@ -4148,7 +4154,7 @@ class AlarmManagerService extends SystemService { 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 100680df637d..b65d126af2b7 100644 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java @@ -497,6 +497,7 @@ public class ForceAppStandbyTracker { /** * 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 @@ public class ForceAppStandbyTracker { /** * Update {@link #mForceAllAppsStandby} and notifies the listeners. */ + @GuardedBy("mLock") private void toggleForceAllAppsStandbyLocked(boolean enable) { if (enable == mForceAllAppsStandby) { return; @@ -545,6 +547,7 @@ public class ForceAppStandbyTracker { mHandler.notifyForceAllAppsStandbyChanged(); } + @GuardedBy("mLock") private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { final int size = mRunAnyRestrictedPackages.size(); if (size > 8) { @@ -563,6 +566,7 @@ public class ForceAppStandbyTracker { /** * @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 @@ public class ForceAppStandbyTracker { /** * Add to / remove from {@link #mRunAnyRestrictedPackages}. */ + @GuardedBy("mLock") boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName, boolean restricted) { final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName); diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 93f7f1d12922..bdeb23163e7e 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -1166,6 +1166,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImePackageAppeared = false; } + @GuardedBy("mMethodMap") private boolean shouldRebuildInputMethodListLocked() { // This method is guaranteed to be called only by getRegisteredHandler(). @@ -1467,6 +1468,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return flags; } + @GuardedBy("mMethodMap") @NonNull InputBindResult attachNewInputLocked( /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) { @@ -1846,6 +1849,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber); } + @GuardedBy("mMethodMap") @NonNull InputBindResult startInputLocked( /* @InputMethodClient.StartInputReason */ final int startInputReason, @@ -1889,6 +1893,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub controlFlags, startInputReason); } + @GuardedBy("mMethodMap") @NonNull InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, /* @InputConnectionInspector.missingMethods */ final int missingMethods, @@ -3642,6 +3647,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 f4238f21f47c..42c836eab7d7 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -199,6 +199,7 @@ public class ServiceWatcher implements ServiceConnection { * 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 @@ public class ServiceWatcher implements ServiceConnection { return true; } + @GuardedBy("mLock") private void unbindLocked() { ComponentName component; component = mBoundComponent; @@ -287,6 +289,7 @@ public class ServiceWatcher implements ServiceConnection { } } + @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 84b93e36c602..1a0068de6852 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -809,6 +809,7 @@ class StorageManagerService extends IStorageManager.Stub } } + @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 @@ class StorageManagerService extends IStorageManager.Stub } }; + @GuardedBy("mLock") private void onDiskScannedLocked(DiskInfo disk) { int volumeCount = 0; for (int i = 0; i < mVolumes.size(); i++) { @@ -1134,6 +1136,7 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub } } + @GuardedBy("mLock") private void onMoveStatusLocked(int status) { if (mMoveCallback == null) { Slog.w(TAG, "Odd, status but no move requested"); @@ -1515,6 +1520,7 @@ class StorageManagerService extends IStorageManager.Stub } } + @GuardedBy("mLock") private void readSettingsLocked() { mRecords.clear(); mPrimaryStorageUuid = getDefaultPrimaryStorageUuid(); @@ -1559,6 +1565,7 @@ class StorageManagerService extends IStorageManager.Stub } } + @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 6743484b91c4..6747be340d46 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -35,7 +35,6 @@ import android.os.UserHandle; import android.telephony.CellInfo; import android.telephony.CellLocation; import android.telephony.DisconnectCause; -import android.telephony.LocationAccessPolicy; import android.telephony.PhoneStateListener; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; @@ -94,8 +93,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { IPhoneStateListener callback; IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; - int callerUid; - int callerPid; + int callerUserId; int events; @@ -119,7 +117,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { + " callback=" + callback + " onSubscriptionsChangedListenererCallback=" + onSubscriptionsChangedListenerCallback - + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId + + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId + " events=" + Integer.toHexString(events) + " canReadPhoneState=" + canReadPhoneState + "}"; } @@ -358,8 +356,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { public void addOnSubscriptionsChangedListener(String callingPackage, IOnSubscriptionsChangedListener callback) { int callerUserId = UserHandle.getCallingUserId(); - mContext.getSystemService(AppOpsManager.class) - .checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId + " callback=" + callback @@ -403,8 +399,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.onSubscriptionsChangedListenerCallback = callback; r.callingPackage = callingPackage; - r.callerUid = Binder.getCallingUid(); - r.callerPid = Binder.getCallingPid(); + r.callerUserId = callerUserId; r.events = 0; r.canReadPhoneState = true; // permission has been enforced above if (DBG) { @@ -475,8 +470,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void listen(String callingPackage, IPhoneStateListener callback, int events, boolean notifyNow, int subId) { int callerUserId = UserHandle.getCallingUserId(); - mContext.getSystemService(AppOpsManager.class) - .checkPackage(Binder.getCallingUid(), callingPackage); if (VDBG) { log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events) + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId=" @@ -521,8 +514,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callback = callback; r.callingPackage = callingPackage; - r.callerUid = Binder.getCallingUid(); - r.callerPid = Binder.getCallingPid(); + r.callerUserId = callerUserId; boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0; r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage); @@ -580,10 +572,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { - r.callback.onCellLocationChanged( - new Bundle(mCellLocation[phoneId])); - } + r.callback.onCellLocationChanged( + new Bundle(mCellLocation[phoneId])); } catch (RemoteException ex) { remove(r.binder); } @@ -629,9 +619,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " + mCellInfo.get(phoneId)); - if (checkLocationAccess(r)) { - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); - } + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } catch (RemoteException ex) { remove(r.binder); } @@ -991,8 +979,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellInfo.set(phoneId, cellInfo); for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) && - idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -1275,8 +1262,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellLocation[phoneId] = cellLocation; for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) && - idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { log("notifyCellLocation: cellLocation=" + cellLocation @@ -1720,11 +1706,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { boolean valid = false; try { foregroundUser = ActivityManager.getCurrentUser(); - valid = UserHandle.getUserId(r.callerUid) == foregroundUser - && r.matchPhoneStateListenerEvent(events); + valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events); if (DBG | DBG_LOC) { log("validateEventsAndUserLocked: valid=" + valid - + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser + + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser + " r.events=" + r.events + " events=" + events); } } finally { @@ -1756,16 +1741,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - private boolean checkLocationAccess(Record r) { - long token = Binder.clearCallingIdentity(); - try { - return LocationAccessPolicy.canAccessCellLocation(mContext, - r.callingPackage, r.callerUid, r.callerPid); - } finally { - Binder.restoreCallingIdentity(token); - } - } - private void checkPossibleMissNotify(Record r, int phoneId) { int events = r.events; @@ -1813,9 +1788,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " + mCellInfo.get(phoneId)); } - if (checkLocationAccess(r)) { - r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); - } + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1863,9 +1836,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { - r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); - } + r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); } catch (RemoteException ex) { mRemoveList.add(r.binder); } diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 48b5a582f74e..8d22eca656cd 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -55,6 +55,7 @@ import android.util.Slog; 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 @@ public class VibratorService extends IVibratorService.Stub 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 @@ public class VibratorService extends IVibratorService.Stub // 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()) { - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration"); + 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; } - 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"); + // 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; } - 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 @@ public class VibratorService extends IVibratorService.Stub } }; + @GuardedBy("mLock") private void doCancelVibrateLocked() { mH.removeCallbacks(mVibrationEndRunnable); if (mThread != null) { @@ -538,6 +541,7 @@ public class VibratorService extends IVibratorService.Stub } } + @GuardedBy("mLock") private void startVibrationLocked(final Vibration vib) { if (!isAllowedToVibrateLocked(vib)) { return; @@ -568,6 +572,7 @@ public class VibratorService extends IVibratorService.Stub startVibrationInnerLocked(vib); } + @GuardedBy("mLock") private void startVibrationInnerLocked(Vibration vib) { mCurrentVibration = vib; if (vib.effect instanceof VibrationEffect.OneShot) { @@ -701,6 +706,7 @@ public class VibratorService extends IVibratorService.Stub return mode; } + @GuardedBy("mLock") private void reportFinishVibrationLocked() { if (mCurrentVibration != null) { try { @@ -880,6 +886,7 @@ public class VibratorService extends IVibratorService.Stub } } + @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 14404f5d4e4f..230f69dd43a0 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1043,20 +1043,14 @@ public final class ActiveServices { 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) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 99f36d0a8b1b..e44223b9a364 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1854,7 +1854,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int REPORT_MEM_USAGE_MSG = 33; static final int IMMERSIVE_MODE_LOCK_MSG = 37; static final int PERSIST_URI_GRANTS_MSG = 38; - static final int REQUEST_ALL_PSS_MSG = 39; static final int UPDATE_TIME_PREFERENCE_MSG = 41; static final int ENTER_ANIMATION_COMPLETE_MSG = 44; static final int FINISH_BOOTING_MSG = 45; @@ -2320,12 +2319,6 @@ public class ActivityManagerService extends IActivityManager.Stub writeGrantedUriPermissions(); break; } - case REQUEST_ALL_PSS_MSG: { - synchronized (ActivityManagerService.this) { - requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); - } - break; - } case UPDATE_TIME_PREFERENCE_MSG: { // The user's time format preference might have changed. // For convenience we re-use the Intent extra values. @@ -2615,11 +2608,17 @@ public class ActivityManagerService extends IActivityManager.Stub procState = proc.pssProcState; statType = proc.pssStatType; lastPssTime = proc.lastPssTime; + long now = SystemClock.uptimeMillis(); if (proc.thread != null && procState == proc.setProcState && (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) - < SystemClock.uptimeMillis()) { + < now) { pid = proc.pid; } else { + ProcessList.abortNextPssTime(proc.procStateMemTracker); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid + + ": still need " + + (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE-now) + + "ms until safe"); proc = null; pid = 0; } @@ -3005,6 +3004,16 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub * 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName, boolean allowWhileBooting, @@ -3827,6 +3839,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * @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 @@ public class ActivityManagerService extends IActivityManager.Stub .supportsLocalVoiceInteraction(); } + @GuardedBy("this") void onLocalVoiceInteractionStartedLocked(IBinder activity, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { ActivityRecord activityToCallback = ActivityRecord.forTokenLocked(activity); @@ -5623,6 +5639,7 @@ public class ActivityManagerService extends IActivityManager.Stub * 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) { if (!mLaunchWarningShown) { mLaunchWarningShown = true; @@ -6656,6 +6676,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, boolean allowRestart, String reason) { final String name = app.processName; @@ -7218,6 +7244,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") private final boolean attachApplicationLocked(IApplicationThread thread, int pid, int callingUid, long startSeq) { @@ -8861,6 +8889,20 @@ public class ActivityManagerService extends IActivityManager.Stub /** * 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub * 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, GrantUri grantUri, final int modeFlags, UriPermissionOwner owner) { if (!Intent.isAccessUriMode(modeFlags)) { @@ -9417,6 +9464,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * 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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Like grantUriPermissionUncheckedLocked, but takes an Intent. */ + @GuardedBy("this") void grantUriPermissionUncheckedFromIntentLocked(NeededUriGrants needed, UriPermissionOwner owner) { if (needed != null) { @@ -9565,6 +9615,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") void grantUriPermissionFromIntentLocked(int callingUid, String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId) { NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg, @@ -9609,6 +9660,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") void removeUriPermissionIfNeededLocked(UriPermission perm) { if (perm.modeFlags == 0) { final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get( @@ -9625,6 +9677,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub * @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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") private void readGrantedUriPermissionsLocked() { if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "readGrantedUriPermissions()"); @@ -10107,6 +10162,7 @@ public class ActivityManagerService extends IActivityManager.Stub * * @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 @@ public class ActivityManagerService extends IActivityManager.Stub long ident = Binder.clearCallingIdentity(); try { synchronized (this) { - startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId), - true /* isSystemCaller */); + final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + + // When starting lock task mode the stack must be in front and focused + task.getStack().moveToFront("startSystemLockTaskMode"); + startLockTaskModeLocked(task, true /* isSystemCaller */); } } finally { Binder.restoreCallingIdentity(ident); @@ -12604,6 +12663,7 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) { ProcessRecord app; @@ -12806,6 +12870,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") void finishRunningVoiceLocked() { if (mRunningVoice != null) { mRunningVoice = null; @@ -12821,6 +12886,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") void updateSleepIfNeededLocked() { final boolean shouldSleep = !mStackSupervisor.hasAwakeDisplay(); final boolean wasSleeping = mSleeping; @@ -12925,6 +12991,7 @@ public class ActivityManagerService extends IActivityManager.Stub Binder.restoreCallingIdentity(origId); } + @GuardedBy("this") void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) { Slog.d(TAG, "<<< startRunningVoiceLocked()"); mVoiceWakeLock.setWorkSource(new WorkSource(targetUid)); @@ -14477,9 +14544,6 @@ public class ActivityManagerService extends IActivityManager.Stub mTestPssMode, isSleepingLocked(), now); } } - - mHandler.removeMessages(REQUEST_ALL_PSS_MSG); - mHandler.sendEmptyMessageDelayed(REQUEST_ALL_PSS_MSG, 2*60*1000); } } @@ -14786,8 +14850,6 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck); } + @GuardedBy("this") void writeProcessesToProtoLocked(ProtoOutputStream proto, String dumpPackage) { int numPers = 0; @@ -17445,6 +17509,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") void dumpPermissionsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; @@ -19517,6 +19582,7 @@ public class ActivityManagerService extends IActivityManager.Stub * @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 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessesToGc.remove(app); mPendingPssProcesses.remove(app); + ProcessList.abortNextPssTime(app.procStateMemTracker); // Dismiss any open dialogs. if (app.crashDialog != null && !app.forceCrashReport) { @@ -20605,6 +20672,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @GuardedBy("this") final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, @@ -21595,6 +21663,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub } try { if (values != null) { - changes = updateGlobalConfiguration(values, initLocale, persistent, userId, + changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId, deferResume); } @@ -21897,8 +21966,16 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Schedule PSS collection of a process. */ - void requestPssLocked(ProcessRecord proc, int procState) { + boolean requestPssLocked(ProcessRecord proc, int procState) { if (mPendingPssProcesses.contains(proc)) { - return; + return false; } if (mPendingPssProcesses.size() == 0) { mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); @@ -23301,6 +23391,7 @@ public class ActivityManagerService extends IActivityManager.Stub proc.pssProcState = procState; proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE; mPendingPssProcesses.add(proc); + return true; } /** @@ -23317,6 +23408,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); mLastFullPssTime = now; mFullPssPending = true; + for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) { + ProcessList.abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker);; + } mPendingPssProcesses.ensureCapacity(mLruProcesses.size()); mPendingPssProcesses.clear(); for (int i = mLruProcesses.size() - 1; i >= 0; i--) { @@ -23325,7 +23419,9 @@ public class ActivityManagerService extends IActivityManager.Stub || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) { continue; } - if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { + if (memLowered || (always && now > + app.lastStateTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) + || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { app.pssProcState = app.setProcState; app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM; @@ -23334,7 +23430,9 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingPssProcesses.add(app); } } - mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); + if (!mBgHandler.hasMessages(COLLECT_PSS_BG_MSG)) { + mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); + } } public void setTestPssMode(boolean enabled) { @@ -23692,7 +23790,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Experimental code to more aggressively collect pss while // running test... the problem is that this tends to collect // the data right when a process is transitioning between process - // states, which well tend to give noisy data. + // states, which will tend to give noisy data. long start = SystemClock.uptimeMillis(); long startTime = SystemClock.currentThreadTimeMillis(); long pss = Debug.getPss(app.pid, mTmpLong, null); @@ -23715,9 +23813,10 @@ public class ActivityManagerService extends IActivityManager.Stub if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL) && now > (app.lastStateTime+ProcessList.minTimeFromStateChange( mTestPssMode)))) { - requestPssLocked(app, app.setProcState); - app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, - app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); + if (requestPssLocked(app, app.setProcState)) { + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, + app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); + } } else if (false && DEBUG_PSS) Slog.d(TAG_PSS, "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now)); } @@ -23984,6 +24083,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub * 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * 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 @@ public class ActivityManagerService extends IActivityManager.Stub /** * 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 cae0d2bc5dd9..4bb18e2b0f01 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.ActivityOptions; 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; @@ -2370,6 +2375,15 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo 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 +2573,23 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo + " 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 ab2dc36d66d0..f3deb8dc5164 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.SparseArray; 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 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * 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 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackSupervisor.mRecentTasks.add(r.getTask()); } + @GuardedBy("mService") private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { if (!mService.mBooting && !mService.mBooted) { // Not ready yet! diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index a0f31cd213b7..831f89a1bdfa 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.util.proto.ProtoOutputStream; 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; @@ -1508,6 +1509,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // Checked. + @GuardedBy("mService") final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, boolean processPausingActivities, Configuration config) { if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index b061ba4106d4..055b89b6a6f0 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 @@ class ActivityStarter { // 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 927b72ceb37e..ef82f36fa79f 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } }; + @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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 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 9c2ee873fa6d..71fd71b87619 100644 --- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java @@ -41,7 +41,7 @@ class GlobalSettingsToPropertiesMapper { // {Settings.Global.SETTING_NAME, "system_property_name"}, {Settings.Global.SYS_VDSO, "sys.vdso"}, {Settings.Global.FPS_DEVISOR, ThreadedRenderer.DEBUG_FPS_DIVISOR}, - {Settings.Global.UID_CPUPOWER, "uid.cpupower"}, + {Settings.Global.DISPLAY_PANEL_LPM, "sys.display_panel_lpm"}, }; @@ -93,7 +93,7 @@ class GlobalSettingsToPropertiesMapper { } 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 52eaca10cb4d..c5edb26892f8 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 @@ public abstract class PersistentConnection<T> { } } + @GuardedBy("mLock") public final void bindInnerLocked(boolean resetBackoff) { unscheduleRebindLocked(); @@ -260,6 +261,7 @@ public abstract class PersistentConnection<T> { } } + @GuardedBy("mLock") private void cleanUpConnectionLocked() { mIsConnected = false; mService = null; @@ -276,6 +278,7 @@ public abstract class PersistentConnection<T> { } } + @GuardedBy("mLock") private final void unbindLocked() { unscheduleRebindLocked(); @@ -289,11 +292,13 @@ public abstract class PersistentConnection<T> { 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 08ee23723df4..bf7aef9ecc65 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -501,19 +501,19 @@ public final class ProcessList { private static final int PSS_FIRST_CACHED_INTERVAL = 20*1000; // The amount of time until PSS when an important process stays in the same state. - private static final int PSS_SAME_PERSISTENT_INTERVAL = 20*60*1000; + private static final int PSS_SAME_PERSISTENT_INTERVAL = 10*60*1000; // The amount of time until PSS when the top process stays in the same state. - private static final int PSS_SAME_TOP_INTERVAL = 5*60*1000; + private static final int PSS_SAME_TOP_INTERVAL = 1*60*1000; // The amount of time until PSS when an important process stays in the same state. - private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000; + private static final int PSS_SAME_IMPORTANT_INTERVAL = 10*60*1000; // The amount of time until PSS when a service process stays in the same state. - private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000; + private static final int PSS_SAME_SERVICE_INTERVAL = 5*60*1000; // The amount of time until PSS when a cached process stays in the same state. - private static final int PSS_SAME_CACHED_INTERVAL = 20*60*1000; + private static final int PSS_SAME_CACHED_INTERVAL = 10*60*1000; // The amount of time until PSS when a persistent process first appears. private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000; @@ -622,16 +622,17 @@ public final class ProcessList { public static final class ProcStateMemTracker { final int[] mHighestMem = new int[PROC_MEM_NUM]; + final float[] mScalingFactor = new float[PROC_MEM_NUM]; int mTotalHighestMem = PROC_MEM_CACHED; - float mCurFactor = 1.0f; int mPendingMemState; int mPendingHighestMemState; - boolean mPendingSame; + float mPendingScalingFactor; public ProcStateMemTracker() { for (int i = PROC_MEM_PERSISTENT; i < PROC_MEM_NUM; i++) { mHighestMem[i] = PROC_MEM_NUM; + mScalingFactor[i] = 1.0f; } mPendingMemState = -1; } @@ -639,16 +640,22 @@ public final class ProcessList { public void dumpLine(PrintWriter pw) { pw.print("best="); pw.print(mTotalHighestMem); - pw.print(" "); - pw.print(mCurFactor); - pw.print("x ("); + pw.print(" ("); + boolean needSep = false; for (int i = 0; i < PROC_MEM_NUM; i++) { - if (i != 0) { - pw.print(", "); + if (mHighestMem[i] < PROC_MEM_NUM) { + if (needSep) { + pw.print(", "); + needSep = false; + } + pw.print(i); + pw.print("="); + pw.print(mHighestMem[i]); + pw.print(" "); + pw.print(mScalingFactor[i]); + pw.print("x"); + needSep = true; } - pw.print(i); - pw.print("="); - pw.print(mHighestMem[i]); } pw.print(")"); if (mPendingMemState >= 0) { @@ -656,8 +663,9 @@ public final class ProcessList { pw.print(mPendingMemState); pw.print(" highest="); pw.print(mPendingHighestMemState); - pw.print(" same="); - pw.print(mPendingSame); + pw.print(" "); + pw.print(mPendingScalingFactor); + pw.print("x"); } pw.println(); } @@ -674,12 +682,8 @@ public final class ProcessList { public static void commitNextPssTime(ProcStateMemTracker tracker) { if (tracker.mPendingMemState >= 0) { tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState; + tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor; tracker.mTotalHighestMem = tracker.mPendingHighestMemState; - if (tracker.mPendingSame) { - tracker.mCurFactor *= 1.5f; - } else { - tracker.mCurFactor = 1; - } tracker.mPendingMemState = -1; } } @@ -691,6 +695,7 @@ public final class ProcessList { public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, boolean sleeping, long now) { boolean first; + float scalingFactor; final int memState = sProcStateToProcMem[procState]; if (tracker != null) { final int highestMemState = memState < tracker.mTotalHighestMem @@ -698,9 +703,15 @@ public final class ProcessList { first = highestMemState < tracker.mHighestMem[memState]; tracker.mPendingMemState = memState; tracker.mPendingHighestMemState = highestMemState; - tracker.mPendingSame = !first; + if (first) { + tracker.mPendingScalingFactor = scalingFactor = 1.0f; + } else { + scalingFactor = tracker.mScalingFactor[memState]; + tracker.mPendingScalingFactor = scalingFactor * 1.5f; + } } else { first = true; + scalingFactor = 1.0f; } final long[] table = test ? (first @@ -709,8 +720,7 @@ public final class ProcessList { : (first ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes) : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes)); - long delay = (long)(table[memState] * (tracker != null && !first - ? tracker.mCurFactor : 1.0f)); + long delay = (long)(table[memState] * scalingFactor); if (delay > PSS_MAX_INTERVAL) { delay = PSS_MAX_INTERVAL; } diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index 8bf320e0c387..c10d81b96cbc 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 @@ public final class ProcessStatsService extends IProcessStats.Stub { return mMemFactorLowered; } + @GuardedBy("mAm") public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) { mMemFactorLowered = memFactor < mLastMemOnlyState; mLastMemOnlyState = memFactor; @@ -237,6 +238,7 @@ public final class ProcessStatsService extends IProcessStats.Stub { if (commit) { mProcessStats.resetSafely(); updateFile(); + mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); } mLastWriteTime = SystemClock.uptimeMillis(); totalTime = SystemClock.uptimeMillis() - now; @@ -784,12 +786,14 @@ public final class ProcessStatsService extends IProcessStats.Stub { } else if ("--reset".equals(arg)) { synchronized (mAm) { mProcessStats.resetSafely(); + mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); pw.println("Process stats reset."); quit = true; } } else if ("--clear".equals(arg)) { synchronized (mAm) { mProcessStats.resetSafely(); + mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); ArrayList<String> files = getCommittedFiles(0, true, true); if (files != null) { for (int fi=0; fi<files.size(); fi++) { diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index e7b067b1ab73..db4e09fb79b8 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.ActivityManager.StackId.INVALID_STACK_ID; 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.app.ActivityOptions; 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks { 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); } finally { mWindowManager.continueSurfaceLayout(); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } } @@ -134,6 +138,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks { 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks { 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 7b0c714b0b4e..d54e2640ceed 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -743,7 +743,7 @@ class UserController implements Handler.Callback { mInjector.stackSupervisorRemoveUser(userId); // Remove the user if it is ephemeral. if (getUserInfo(userId).isEphemeral()) { - mInjector.getUserManager().removeUser(userId); + mInjector.getUserManager().removeUserEvenWhenDisallowed(userId); } // Evict the user's credential encryption key. try { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 8635cff7ecf9..e95608f16203 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -285,7 +285,7 @@ public class AudioService extends IAudioService.Stub 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 @@ -300,7 +300,7 @@ public class AudioService extends IAudioService.Stub }; /** 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 @@ -1410,7 +1410,7 @@ public class AudioService extends IAudioService.Stub 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; diff --git a/services/core/java/com/android/server/car/CarServiceHelperService.java b/services/core/java/com/android/server/car/CarServiceHelperService.java deleted file mode 100644 index 9392a6a8fcd9..000000000000 --- a/services/core/java/com/android/server/car/CarServiceHelperService.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.car; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcel; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Slog; - -import com.android.internal.car.ICarServiceHelper; -import com.android.server.SystemService; - -/** - * System service side companion service for CarService. - * Starts car service and provide necessary API for CarService. Only for car product. - */ -public class CarServiceHelperService extends SystemService { - private static final String TAG = "CarServiceHelper"; - private static final String CAR_SERVICE_INTERFACE = "android.car.ICar"; - private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl(); - private IBinder mCarService; - private final ServiceConnection mCarServiceConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - Slog.i(TAG, "**CarService connected**"); - mCarService = iBinder; - // Cannot depend on ICar which is defined in CarService, so handle binder call directly - // instead. - // void setCarServiceHelper(in IBinder helper) - Parcel data = Parcel.obtain(); - data.writeInterfaceToken(CAR_SERVICE_INTERFACE); - data.writeStrongBinder(mHelper.asBinder()); - try { - mCarService.transact(IBinder.FIRST_CALL_TRANSACTION, // setCarServiceHelper - data, null, Binder.FLAG_ONEWAY); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException from car service", e); - handleCarServiceCrash(); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - handleCarServiceCrash(); - } - }; - - public CarServiceHelperService(Context context) { - super(context); - } - - @Override - public void onStart() { - Intent intent = new Intent(); - intent.setPackage("com.android.car"); - intent.setAction(CAR_SERVICE_INTERFACE); - if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE, - UserHandle.SYSTEM)) { - Slog.wtf(TAG, "cannot start car service"); - } - } - - private void handleCarServiceCrash() { - //TODO define recovery bahavior - } - - private class ICarServiceHelperImpl extends ICarServiceHelper.Stub { - //TODO - } -} diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 1ee0548a5bf7..c3f020ade635 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 @@ public final class ContentService extends IContentService.Stub { 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 @@ public final class ContentService extends IContentService.Stub { 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/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java index 75c01819a9ea..20aec7e4ae29 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 @@ public class SyncLogger { } } + @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 @@ public class SyncLogger { } } + @GuardedBy("mLock") private void closeCurrentLogLocked() { IoUtils.closeQuietly(mLogWriter); mLogWriter = null; diff --git a/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java b/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java index 0976a22f5e13..b0cde2dd65ae 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 @@ class FingerprintsUserState { } } + @GuardedBy("this") private void readStateSyncLocked() { FileInputStream in; if (!mFile.exists()) { @@ -226,6 +227,7 @@ class FingerprintsUserState { } } + @GuardedBy("this") private void parseStateLocked(XmlPullParser parser) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); @@ -243,6 +245,7 @@ class FingerprintsUserState { } } + @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 1e09383db56d..de0f29851da5 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 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { /** * Return external input devices. */ + @GuardedBy("mLock") List<HdmiDeviceInfo> getSafeExternalInputsLocked() { return mSafeExternalInputs; } @@ -1421,6 +1422,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } + @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 3d079ccb0cad..b06dba9f9627 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 @@ public final class HdmiControlService extends SystemService { } } + @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 37b39907e976..4988974e95db 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 @@ public final class JobServiceContext implements ServiceConnection { } /** 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 @@ public final class JobServiceContext implements ServiceConnection { return mTimeoutElapsed; } + @GuardedBy("mLock") boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId, String reason) { final JobStatus executing = getRunningJobLocked(); @@ -512,6 +515,7 @@ public final class JobServiceContext implements ServiceConnection { } } + @GuardedBy("mLock") void doServiceBoundLocked() { removeOpTimeOutLocked(); handleServiceBoundLocked(); @@ -531,6 +535,7 @@ public final class JobServiceContext implements ServiceConnection { } } + @GuardedBy("mLock") void doCallbackLocked(boolean reschedule, String reason) { if (DEBUG) { Slog.d(TAG, "doCallback of : " + mRunningJob @@ -550,6 +555,7 @@ public final class JobServiceContext implements ServiceConnection { } } + @GuardedBy("mLock") void doCancelLocked(int arg1, String debugReason) { if (mVerb == VERB_FINISHED) { if (DEBUG) { @@ -567,6 +573,7 @@ public final class JobServiceContext implements ServiceConnection { } /** Start the job on the service. */ + @GuardedBy("mLock") private void handleServiceBoundLocked() { if (DEBUG) { Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked()); @@ -605,6 +612,7 @@ public final class JobServiceContext implements ServiceConnection { * _EXECUTING -> Error * _STOPPING -> Error */ + @GuardedBy("mLock") private void handleStartedLocked(boolean workOngoing) { switch (mVerb) { case VERB_STARTING: @@ -637,6 +645,7 @@ public final class JobServiceContext implements ServiceConnection { * _STARTING -> Error * _PENDING -> Error */ + @GuardedBy("mLock") private void handleFinishedLocked(boolean reschedule, String reason) { switch (mVerb) { case VERB_EXECUTING: @@ -659,6 +668,7 @@ public final class JobServiceContext implements ServiceConnection { * 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 @@ public final class JobServiceContext implements ServiceConnection { } /** Process MSG_TIMEOUT here. */ + @GuardedBy("mLock") private void handleOpTimeoutLocked() { switch (mVerb) { case VERB_BINDING: @@ -722,6 +733,7 @@ public final class JobServiceContext implements ServiceConnection { * 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 @@ public final class JobServiceContext implements ServiceConnection { * 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 13873e476894..77e813e086ce 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 @@ public final class ConnectivityController extends StateController implements mNetPolicyManager.registerListener(mNetPolicyListener); } + @GuardedBy("mLock") @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { if (jobStatus.hasConnectivityConstraint()) { @@ -101,6 +102,7 @@ public final class ConnectivityController extends StateController implements } } + @GuardedBy("mLock") @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { @@ -325,6 +327,7 @@ public final class ConnectivityController extends StateController implements } }; + @GuardedBy("mLock") @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { pw.print("Connectivity: connected="); @@ -348,6 +351,7 @@ public final class ConnectivityController extends StateController implements } } + @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 e71572483cca..6deff3652944 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 @@ public class LockSettingsService extends ILockSettings.Stub { * 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { : "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 662ffc814390..dee24c7c0450 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.NoSuchAlgorithmException; 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 @@ public class KeySyncTask implements Runnable { 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 3a78f950675e..e5ff5b83704f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -194,6 +194,9 @@ public class PlatformKeyManager { UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { init(userId); try { + // Try to see if the decryption key is still accessible before using the encryption key. + // The auth-bound decryption will be unrecoverable if the screen lock is disabled. + getDecryptKeyInternal(userId); return getEncryptKeyInternal(userId); } catch (UnrecoverableKeyException e) { Log.i(TAG, String.format(Locale.US, @@ -219,7 +222,7 @@ public class PlatformKeyManager { UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { int generationId = getGenerationId(userId); String alias = getEncryptAlias(userId, generationId); - if (!mKeyStore.containsAlias(alias)) { + if (!isKeyLoaded(userId, generationId)) { throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); } AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( @@ -268,7 +271,7 @@ public class PlatformKeyManager { UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { int generationId = getGenerationId(userId); String alias = getDecryptAlias(userId, generationId); - if (!mKeyStore.containsAlias(alias)) { + if (!isKeyLoaded(userId, generationId)) { throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); } AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( @@ -300,12 +303,12 @@ public class PlatformKeyManager { return; } if (generationId == -1) { - Log.i(TAG, "Generating initial platform ID."); + Log.i(TAG, "Generating initial platform key generation ID."); generationId = 1; } else { Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no " + "entry was present in AndroidKeyStore. Generating fresh key.", generationId)); - // Had to generate a fresh key, bump the generation id + // Have to generate a fresh key, so bump the generation id generationId++; } @@ -374,7 +377,7 @@ public class PlatformKeyManager { String decryptAlias = getDecryptAlias(userId, generationId); SecretKey secretKey = generateAesKey(); - // Store Since decryption key first since it is more likely to fail. + // Store decryption key first since it is more likely to fail. mKeyStore.setEntry( decryptAlias, new KeyStore.SecretKeyEntry(secretKey), @@ -386,7 +389,6 @@ public class PlatformKeyManager { .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setBoundToSpecificSecureUserId(userId) .build()); - mKeyStore.setEntry( encryptAlias, new KeyStore.SecretKeyEntry(secretKey), @@ -397,11 +399,7 @@ public class PlatformKeyManager { setGenerationId(userId, generationId); - try { - secretKey.destroy(); - } catch (DestroyFailedException e) { - Log.w(TAG, "Failed to destroy in-memory platform key.", e); - } + // TODO: Use a reliable way to destroy the temporary secretKey in memory. } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index fda6cdf33e0c..9f822688a8c0 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_NO_SNAPSHOT_PEN 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 android.util.Log; 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.KeyFactory; 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 class RecoverableKeyStoreManager { } 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 @@ public class RecoverableKeyStoreManager { 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 @@ public class RecoverableKeyStoreManager { byte[] locallyEncryptedKey; try { // TODO: Remove the extraneous logging here - Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()", + Log.d(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()", sessionEntry.getKeyClaimant())); - Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()", + Log.d(TAG, constructLoggingMessage("sessionEntry.getVaultParams()", sessionEntry.getVaultParams())); - Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse)); + Log.d(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse)); locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( sessionEntry.getKeyClaimant(), sessionEntry.getVaultParams(), @@ -543,9 +593,9 @@ public class RecoverableKeyStoreManager { try { // TODO: Remove the extraneous logging here - Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()", + Log.d(TAG, constructLoggingMessage("sessionEntry.getLskfHash()", sessionEntry.getLskfHash())); - Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey)); + Log.d(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey)); return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); } catch (InvalidKeyException e) { Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e); @@ -585,8 +635,8 @@ public class RecoverableKeyStoreManager { try { // TODO: Remove the extraneous logging here - Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); - Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); + Log.d(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); + Log.d(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial); keyMaterialByAlias.put(alias, keyMaterial); @@ -600,13 +650,16 @@ public class RecoverableKeyStoreManager { throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to recover key with alias '" + alias + "': " + e.getMessage()); } catch (AEADBadTagException e) { - // TODO: Remove the extraneous logging here Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: " + alias, e); - throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, - "Failed to recover key with alias '" + alias + "': " + e.getMessage()); + // Ignore the exception to continue to recover the other application keys. } } + if (keyMaterialByAlias.isEmpty()) { + Log.e(TAG, "Failed to recover any of the application keys."); + throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, + "Failed to recover any of the application keys."); + } return keyMaterialByAlias; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java index fea6733dd1f5..09ec5ad1d5dd 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 @@ -68,7 +68,7 @@ import org.w3c.dom.NodeList; 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"; @@ -217,7 +217,7 @@ final class CertUtils { * @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/TrustedRootCert.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java new file mode 100644 index 000000000000..7195d628e4bd --- /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 b96208d66b01..89ddb6c9eb30 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.RecoverableKe 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 @@ public class RecoverableKeyStoreDb { 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 @@ public class RecoverableKeyStoreDb { } /** + * 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 @@ public class RecoverableKeyStoreDb { } /** - * 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 @@ public class RecoverableKeyStoreDb { 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 @@ public class RecoverableKeyStoreDb { 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 4ee282b6115e..1cb5d91be3ba 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 @@ class RecoverableKeyStoreDbContract { 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 @@ class RecoverableKeyStoreDbContract { 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 79fd496ad11c..8a89f2d4faa9 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 @@ package com.android.server.locksettings.recoverablekeystore.storage; 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 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe * 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { @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 7881a952eab9..648c782a7d1e 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 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { } } + @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 a6f049e1d8d0..f34662909a85 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index bfc150e1a9b0..76c4db134036 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags); } + @GuardedBy("mStatsLock") private void shutdownLocked() { mContext.unregisterReceiver(mTetherReceiver); mContext.unregisterReceiver(mPollReceiver); @@ -431,6 +432,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mSystemReady = false; } + @GuardedBy("mStatsLock") private void maybeUpgradeLegacyStatsLocked() { File file; try { @@ -909,6 +911,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + @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 c4de4ac1b7dc..81fe641a6409 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -128,7 +128,7 @@ class WatchlistLoggingHandler extends Handler { Slog.e(TAG, "Couldn't find package: " + packageNames); return false; } - ai = mPm.getApplicationInfo(packageNames[0],0); + ai = mPm.getApplicationInfo(packageNames[0], 0); } catch (NameNotFoundException e) { // Should not happen. return false; @@ -136,7 +136,7 @@ class WatchlistLoggingHandler extends Handler { return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; } - /** + /** * Report network watchlist records if we collected enough data. */ public void reportWatchlistIfNecessary() { @@ -180,6 +180,10 @@ class WatchlistLoggingHandler extends Handler { return true; } final byte[] digest = getDigestFromUid(uid); + if (digest == null) { + Slog.e(TAG, "Cannot get digest from uid: " + uid); + return false; + } final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp); tryAggregateRecords(); return result; @@ -242,6 +246,11 @@ class WatchlistLoggingHandler extends Handler { final int size = apps.size(); for (int i = 0; i < size; i++) { byte[] digest = getDigestFromUid(apps.get(i).uid); + if (digest == null) { + Slog.e(TAG, "Cannot get digest from uid: " + apps.get(i).uid + + ",pkg: " + apps.get(i).packageName); + continue; + } result.add(HexDump.toHexString(digest)); } // Step 2: Add all digests from records diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ada002c0c5b0..3800017f0a08 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -686,6 +686,7 @@ public class NotificationManagerService extends SystemService { sbn.getId(), Notification.FLAG_AUTO_CANCEL, Notification.FLAG_FOREGROUND_SERVICE, false, r.getUserId(), REASON_CLICK, null); + reportUserInteraction(r); } } @@ -706,7 +707,7 @@ public class NotificationManagerService extends SystemService { .setSubtype(actionIndex)); EventLogTags.writeNotificationActionClicked(key, actionIndex, r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now)); - // TODO: Log action click via UsageStats. + reportUserInteraction(r); } } @@ -827,6 +828,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { r.recordDirectReplied(); + reportUserInteraction(r); } } } @@ -1758,6 +1760,10 @@ public class NotificationManagerService extends SystemService { 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(), @@ -1766,6 +1772,17 @@ public class NotificationManagerService extends SystemService { 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; @@ -3909,6 +3926,7 @@ public class NotificationManagerService extends SystemService { return true; } + @GuardedBy("mNotificationLock") protected int getNotificationCountLocked(String pkg, int userId, int excludedId, String excludedTag) { int count = 0; diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 6e898bb99987..bc9fa4b7e32b 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -126,17 +126,16 @@ public abstract class InstantAppResolver { final Intent origIntent = requestObj.origIntent; final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); - final int[] shaPrefix = digest.getDigestPrefix(); AuxiliaryResolveInfo resolveInfo = null; @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; try { final List<InstantAppResolveInfo> instantAppResolveInfoList = - connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token); + connection.getInstantAppResolveInfoList(sanitizedIntent, + requestObj.digest.getDigestPrefixSecure(), token); if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { resolveInfo = InstantAppResolver.filterInstantAppIntent( instantAppResolveInfoList, origIntent, requestObj.resolvedType, - requestObj.userId, origIntent.getPackage(), digest, token); + requestObj.userId, origIntent.getPackage(), requestObj.digest, token); } } catch (ConnectionException e) { if (e.failure == ConnectionException.FAILURE_BIND) { @@ -166,12 +165,6 @@ public abstract class InstantAppResolver { return resolveInfo; } - private static InstantAppDigest getInstantAppDigest(Intent origIntent) { - return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost()) - ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/) - : InstantAppDigest.UNDEFINED; - } - public static void doInstantAppResolutionPhaseTwo(Context context, InstantAppResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { @@ -182,8 +175,6 @@ public abstract class InstantAppResolver { } final Intent origIntent = requestObj.origIntent; final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); - final int[] shaPrefix = digest.getDigestPrefix(); final PhaseTwoCallback callback = new PhaseTwoCallback() { @Override @@ -194,7 +185,8 @@ public abstract class InstantAppResolver { final AuxiliaryResolveInfo instantAppIntentInfo = InstantAppResolver.filterInstantAppIntent( instantAppResolveInfoList, origIntent, null /*resolvedType*/, - 0 /*userId*/, origIntent.getPackage(), digest, token); + 0 /*userId*/, origIntent.getPackage(), requestObj.digest, + token); if (instantAppIntentInfo != null) { failureIntent = instantAppIntentInfo.failureIntent; } else { @@ -225,8 +217,9 @@ public abstract class InstantAppResolver { } }; try { - connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback, - callbackHandler, startTime); + connection.getInstantAppIntentFilterList(sanitizedIntent, + requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler, + startTime); } catch (ConnectionException e) { @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; if (e.failure == ConnectionException.FAILURE_BIND) { diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java index a9ee41162ae1..98f421ecf2b8 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 @@ final class InstantAppResolverConnection implements DeathRecipient { } } + @GuardedBy("mLock") private void waitForBindLocked(String token) throws TimeoutException, InterruptedException { final long startMillis = SystemClock.uptimeMillis(); while (mBindState != STATE_IDLE) { @@ -250,6 +251,7 @@ final class InstantAppResolverConnection implements DeathRecipient { } } + @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 59f9dae0619a..0b32d1a5d69b 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + @GuardedBy("mSessions") private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) { final File stagingDir = buildStagingDir(volumeUuid, isEphemeral); final ArraySet<File> unclaimedStages = newArraySet( @@ -283,6 +284,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + @GuardedBy("mSessions") private void readSessionsLocked() { if (LOGD) Slog.v(TAG, "readSessionsLocked()"); @@ -340,6 +342,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + @GuardedBy("mSessions") private void addHistoricalSessionLocked(PackageInstallerSession session) { CharArrayWriter writer = new CharArrayWriter(); IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -352,6 +355,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mHistoricalSessionsByInstaller.get(installerUid) + 1); } + @GuardedBy("mSessions") private void writeSessionsLocked() { if (LOGD) Slog.v(TAG, "writeSessionsLocked()"); @@ -612,6 +616,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + @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 3dd5a3415e35..9c692816aa6e 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** * @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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * * @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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") private void assertPreparedAndNotSealedLocked(String cookie) { assertPreparedAndNotCommittedOrDestroyedLocked(cookie); if (mSealed) { @@ -463,6 +466,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) { assertPreparedAndNotDestroyedLocked(cookie); if (mCommitted) { @@ -470,6 +474,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") private void assertPreparedAndNotDestroyedLocked(String cookie) { if (!mPrepared) { throw new IllegalStateException(cookie + " before prepared"); @@ -484,6 +489,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** * 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mCallback.onSessionSealedBlocking(this); } + @GuardedBy("mLock") private void commitLocked() throws PackageManagerException { if (mDestroyed) { @@ -1016,6 +1027,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @GuardedBy("mLock") private void assertApkConsistentLocked(String tag, ApkLite apk) throws PackageManagerException { if (!mPackageName.equals(apk.packageName)) { @@ -1511,6 +1524,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + @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 2816bbd36b0b..384b074e4c2f 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 @@ public class PackageManagerService extends IPackageManager.Stub return overlayPackages; } + @GuardedBy("mInstallLock") final String[] getStaticOverlayPathsLocked(Collection<PackageParser.Package> allPackages, String targetPackageName, String targetPath) { if ("android".equals(targetPackageName)) { @@ -9015,6 +9016,7 @@ public class PackageManagerService extends IPackageManager.Stub } } + @GuardedBy("mPackages") private void notifyPackageUseLocked(String packageName, int reason) { final PackageParser.Package p = mPackages.get(packageName); if (p == null) { @@ -10625,8 +10627,6 @@ public class PackageManagerService extends IPackageManager.Stub ~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) { @@ -10635,6 +10635,8 @@ public class PackageManagerService extends IPackageManager.Stub } } 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) { @@ -13954,6 +13956,7 @@ public class PackageManagerService extends IPackageManager.Stub } } + @GuardedBy("mPackages") private boolean canSuspendPackageForUserLocked(String packageName, int userId) { if (isPackageDeviceAdmin(packageName, userId)) { Slog.w(TAG, "Cannot suspend/un-suspend package \"" + packageName @@ -16610,6 +16613,12 @@ public class PackageManagerService extends IPackageManager.Stub 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/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index a85d6d838045..034fd2390a8c 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid)); } + @GuardedBy("mLock") long getUidLastForegroundElapsedTimeLocked(int uid) { return mUidLastForegroundElapsedTime.get(uid); } @@ -638,6 +640,7 @@ public class ShortcutService extends IShortcutService.Stub { } } + @GuardedBy("mLock") private void unloadUserLocked(int userId) { if (DEBUG) { Slog.d(TAG, "unloadUserLocked: user=" + userId); @@ -864,6 +867,7 @@ public class ShortcutService extends IShortcutService.Stub { writeAttr(out, name, intent.toUri(/* flags =*/ 0)); } + @GuardedBy("mLock") @VisibleForTesting void saveBaseStateLocked() { final AtomicFile file = getBaseStateFile(); @@ -896,6 +900,7 @@ public class ShortcutService extends IShortcutService.Stub { } } + @GuardedBy("mLock") private void loadBaseStateLocked() { mRawLastResetTime = 0; @@ -948,6 +953,7 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } } + @GuardedBy("mLock") private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, boolean forBackup) throws IOException, XmlPullParserException { @@ -1107,12 +1114,14 @@ public class ShortcutService extends IShortcutService.Stub { } /** 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 @@ public class ShortcutService extends IShortcutService.Stub { /** * Update the last reset time. */ + @GuardedBy("mLock") private void updateTimesLocked() { final long now = injectCurrentTimeMillis(); @@ -1220,6 +1230,7 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { * {@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 @@ public class ShortcutService extends IShortcutService.Stub { } } + @GuardedBy("mLock") private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { @@ -2418,6 +2431,7 @@ public class ShortcutService extends IShortcutService.Stub { * * 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } } + @GuardedBy("ShortcutService.this.mLock") private ShortcutInfo getShortcutInfoLocked( int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @@ -2940,6 +2956,7 @@ public class ShortcutService extends IShortcutService.Stub { 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 b53d83b1291c..a0577b15736b 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { } } + @GuardedBy("mUsersLock") @VisibleForTesting void addRemovingUserIdLocked(int userId) { // We remember deleted user IDs to prevent them from being @@ -3405,6 +3407,7 @@ public class UserManagerService extends IUserManager.Stub { 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/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 08f8bbd20af6..3f8a1e8a6af1 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 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); return false; } + @GuardedBy("mLock") private void grantRuntimePermissionsGrantedToDisabledPackageLocked( PackageParser.Package pkg, int callingUid, PermissionCallback callback) { if (pkg.parentPackage == null) { @@ -1499,6 +1500,7 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); } } + @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 f6c4990c3294..b3f2833a52f1 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 @@ public class PermissionSettings { } } + @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 177d6af489b3..0502848d698e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5603,7 +5603,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int fl = PolicyControl.getWindowFlags(null, mTopFullscreenOpaqueWindowState.getAttrs()); if (localLOGV) { - Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()); + Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() + + " shown position: " + + mTopFullscreenOpaqueWindowState.getShownPositionLw()); Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs() + " lp.flags=0x" + Integer.toHexString(fl)); } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 3af3fcbbf7a8..e9c4c5c8138f 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -232,6 +232,14 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public Rect getFrameLw(); /** + * Retrieve the current position of the window that is actually shown. + * Must be called with the window manager lock held. + * + * @return Point The point holding the shown window position. + */ + public Point getShownPositionLw(); + + /** * Retrieve the frame of the display that this window was last * laid out in. Must be called with the * window manager lock held. diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index 847c90a08c5c..08dc97e7dd63 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 @@ public class BatterySaverPolicy extends ContentObserver { } } + @GuardedBy("mLock") @VisibleForTesting void updateConstantsLocked(final String setting, final String deviceSpecificSetting) { mSettings = setting; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 1bb85c4426e1..38dc820e5b88 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 @@ public final class PowerManagerService extends SystemService 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,14 +1740,15 @@ public final class PowerManagerService extends SystemService 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); - } else if (mIsPowered && !wasPowered - && (mPlugType == BatteryManager.BATTERY_PLUGGED_AC - || mPlugType == BatteryManager.BATTERY_PLUGGED_USB)) { - mNotifier.onWiredChargingStarted(); + // 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 b0b07ea767f6..37df94fee207 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 @@ public class BatterySavingStats { } } + @GuardedBy("mLock") private void transitionStateLocked(int newState) { if (mCurrentState == newState) { return; @@ -298,6 +299,7 @@ public class BatterySavingStats { 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 @@ public class BatterySavingStats { } + @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 @@ public class BatterySavingStats { 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 e0ab9e93a8a6..c08b610ccd2d 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 @@ public class FileUpdater { } } + @GuardedBy("mLock") private void saveDefaultValuesLocked() { final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); @@ -334,6 +335,7 @@ public class FileUpdater { } } + @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 c280739f2e1a..ef6067a73716 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 class StatsCompanionService extends IStatsCompanionService.Stub { public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; static final String TAG = "StatsCompanionService"; - static final boolean DEBUG = true; - - public static final String ACTION_TRIGGER_COLLECTION = - "com.android.server.stats.action.TRIGGER_COLLECTION"; + static final boolean DEBUG = false; + public static final int CODE_DATA_BROADCAST = 1; public static final int CODE_SUBSCRIBER_BROADCAST = 1; private final Context mContext; @@ -110,9 +108,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } // 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - private final static class AnomalyAlarmReceiver extends BroadcastReceiver { + public final static class AnomalyAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred."); @@ -304,25 +307,24 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { * 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @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); - } - e.writeLong(entry.rxBytes); - e.writeLong(entry.rxPackets); - e.writeLong(entry.txBytes); - e.writeLong(entry.txPackets); - ret.add(e); - } + 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); + } } /** @@ -491,220 +493,220 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } 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; - } - 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); - } + 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); + } } 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; - } - 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); - } + 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); + } } 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; - } - 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); - } + 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); + } } 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; - } - 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); - } + 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); + } } 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @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 7c170aee92fa..343fb91eec53 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 class StatusBarManagerService extends IStatusBarService.Stub { 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 853c7eb51b84..0ac853b030e1 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.Context; 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 com.android.server.SystemService; 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 @@ public final class TextClassificationManagerService extends ITextClassifierServi 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 @@ public final class TextClassificationManagerService extends ITextClassifierServi } 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 @@ public final class TextClassificationManagerService extends ITextClassifierServi 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 @@ public final class TextClassificationManagerService extends ITextClassifierServi } }; mPendingRequests = new LinkedList<>(); - mUnbind = this::unbind; mLock = new Object(); } @@ -152,7 +143,6 @@ public final class TextClassificationManagerService extends ITextClassifierServi if (isBoundLocked()) { mService.onSuggestSelection( text, selectionStartIndex, selectionEndIndex, options, callback); - scheduleUnbindLocked(); } else { final Callable<Void> request = () -> { onSuggestSelection( @@ -184,7 +174,6 @@ public final class TextClassificationManagerService extends ITextClassifierServi 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 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { if (isBoundLocked()) { mService.onGenerateLinks(text, options, callback); - scheduleUnbindLocked(); } else { final Callable<Void> request = () -> { onGenerateLinks(text, options, callback); @@ -270,27 +258,6 @@ public final class TextClassificationManagerService extends ITextClassifierServi 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/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index fe5b65ccbf6d..e869f582ed1f 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 @@ public class RecentsAnimationController { // 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 @@ public class RecentsAnimationController { 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 @@ public class RecentsAnimationController { * @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 @@ public class RecentsAnimationController { 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 @@ public class RecentsAnimationController { 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 @@ public class RecentsAnimationController { } 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 925199358b4e..c353c1d36d60 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -166,7 +166,7 @@ class RemoteAnimationController { } 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/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index dc62cc89c14d..1b2f9542df11 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 @@ class SurfaceAnimationRunner { } } + @GuardedBy("mLock") private void startPendingAnimationsLocked() { for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { startAnimationLocked(mPendingAnimations.valueAt(i)); @@ -158,6 +159,7 @@ class SurfaceAnimationRunner { 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 7b047a80d572..621bee7d17e0 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 @@ class TaskSnapshotPersister { 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 da3a035ad8df..f2ad6fb7a888 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -272,8 +272,6 @@ class WallpaperController { } boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) { - int xOffset = 0; - int yOffset = 0; boolean rawChanged = false; // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to // match the behavior of most Launchers @@ -285,8 +283,11 @@ class WallpaperController { if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { offset += mLastWallpaperDisplayOffsetX; } - xOffset = offset; - + boolean changed = wallpaperWin.mXOffset != offset; + if (changed) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " x: " + offset); + wallpaperWin.mXOffset = offset; + } if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) { wallpaperWin.mWallpaperX = wpx; wallpaperWin.mWallpaperXStep = wpxs; @@ -300,16 +301,17 @@ class WallpaperController { if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { offset += mLastWallpaperDisplayOffsetY; } - yOffset = offset; - + if (wallpaperWin.mYOffset != offset) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset); + changed = true; + wallpaperWin.mYOffset = offset; + } if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) { wallpaperWin.mWallpaperY = wpy; wallpaperWin.mWallpaperYStep = wpys; rawChanged = true; } - boolean changed = wallpaperWin.mWinAnimator.setWallpaperOffset(xOffset, yOffset); - if (rawChanged && (wallpaperWin.mAttrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) { try { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index ddda027595da..2ae5c7bd9c25 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -74,6 +74,10 @@ class WallpaperWindowToken extends WindowToken { for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { final WindowState wallpaper = mChildren.get(wallpaperNdx); if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) { + final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; + winAnimator.computeShownFrameLocked(); + // No need to lay out the windows - we can just set the wallpaper position directly. + winAnimator.setWallpaperOffset(wallpaper.mShownPosition); // We only want to be synchronous with one wallpaper. sync = false; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 48d29e372124..b6712c097e23 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -137,6 +137,7 @@ import static com.android.server.wm.proto.WindowStateProto.REMOVED; import static com.android.server.wm.proto.WindowStateProto.REMOVE_ON_EXIT; import static com.android.server.wm.proto.WindowStateProto.REQUESTED_HEIGHT; import static com.android.server.wm.proto.WindowStateProto.REQUESTED_WIDTH; +import static com.android.server.wm.proto.WindowStateProto.SHOWN_POSITION; import static com.android.server.wm.proto.WindowStateProto.STABLE_INSETS; import static com.android.server.wm.proto.WindowStateProto.STACK_ID; import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS; @@ -264,7 +265,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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; @@ -296,6 +297,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration(); /** + * Actual position of the surface shown on-screen (may be modified by animation). These are + * in the screen's coordinate space (WITH the compatibility scale applied). + */ + final Point mShownPosition = new Point(); + + /** * Insets that determine the actually visible area. These are in the application's * coordinate space (without compatibility scale applied). */ @@ -454,6 +461,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Wallpaper windows: pixels offset based on above variables. + int mXOffset; + int mYOffset; + /** * This is set after IWindowSession.relayout() has been called at * least once for the window. It allows us to detect the situation @@ -734,6 +745,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mRequestedHeight = 0; mLastRequestedWidth = 0; mLastRequestedHeight = 0; + mXOffset = 0; + mYOffset = 0; mLayer = 0; mInputWindowHandle = new InputWindowHandle( mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c, @@ -1099,6 +1112,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override + public Point getShownPositionLw() { + return mShownPosition; + } + + @Override public Rect getDisplayFrameLw() { return mDisplayFrame; } @@ -3116,6 +3134,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mContentInsets.writeToProto(proto, CONTENT_INSETS); mAttrs.surfaceInsets.writeToProto(proto, SURFACE_INSETS); mSurfacePosition.writeToProto(proto, SURFACE_POSITION); + mShownPosition.writeToProto(proto, SHOWN_POSITION); mWinAnimator.writeToProto(proto, ANIMATOR); proto.write(ANIMATING_EXIT, mAnimatingExit); for (int i = 0; i < mChildren.size(); i++) { @@ -3231,6 +3250,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled); pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded); } + if (mXOffset != 0 || mYOffset != 0) { + pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset); + pw.print(" y="); pw.println(mYOffset); + } if (dumpAll) { pw.print(prefix); pw.print("mGivenContentInsets="); mGivenContentInsets.printShortString(pw); @@ -3249,6 +3272,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(getLastReportedConfiguration()); } pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); + pw.print(" mShownPosition="); mShownPosition.printShortString(pw); pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay()); pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed); if (dumpAll) { @@ -4171,8 +4195,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int width = mFrame.width(); final int height = mFrame.height(); - final int left = mFrame.left; - final int top = mFrame.top; + // Compute the offset of the window in relation to the decor rect. + final int left = mXOffset + mFrame.left; + final int top = mYOffset + mFrame.top; // Initialize the decor rect to the entire frame. if (isDockedResizing()) { @@ -4367,8 +4392,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx; float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy; float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy; - int x = mSurfacePosition.x; - int y = mSurfacePosition.y; + int x = mSurfacePosition.x + mShownPosition.x; + int y = mSurfacePosition.y + mShownPosition.y; // If changed, also adjust transformFrameToSurfacePosition final WindowContainer parent = getParent(); @@ -4480,13 +4505,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (!mAnimatingExit && mAppDied) { mIsDimming = true; dimmer.dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); - } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && isVisibleNow() - && !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 WSA is not hidden. + // 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 499322c0b17a..dd23b6f25630 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -209,14 +209,10 @@ class WindowStateAnimator { float mExtraHScale = (float) 1.0; float mExtraVScale = (float) 1.0; - // An offset in pixel of the surface contents from the window position. Used for Wallpaper - // to provide the effect of scrolling within a large surface. We just use these values as - // a cache. - int mXOffset = 0; - int mYOffset = 0; - private final Rect mTmpSize = new Rect(); + private final SurfaceControl.Transaction mReparentTransaction = new SurfaceControl.Transaction(); + WindowStateAnimator(final WindowState win) { final WindowManagerService service = win.mService; @@ -377,9 +373,9 @@ class WindowStateAnimator { // child layers need to be reparented to the new surface to make this // transparent to the app. if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) { - SurfaceControl.openTransaction(); - mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController); - SurfaceControl.closeTransaction(); + mReparentTransaction.reparentChildren(mPendingDestroySurface.mSurfaceControl, + mSurfaceController.mSurfaceControl.getHandle()) + .apply(); } } } @@ -442,7 +438,7 @@ class WindowStateAnimator { flags |= SurfaceControl.SECURE; } - mTmpSize.set(0, 0, 0, 0); + mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0); calculateSurfaceBounds(w, attrs); final int width = mTmpSize.width(); final int height = mTmpSize.height(); @@ -593,7 +589,7 @@ class WindowStateAnimator { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { WindowManagerService.logSurface(mWin, "DESTROY PENDING", true); } - mPendingDestroySurface.destroyInTransaction(); + mPendingDestroySurface.destroyNotInTransaction(); } mPendingDestroySurface = mSurfaceController; } @@ -630,7 +626,7 @@ class WindowStateAnimator { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { WindowManagerService.logSurface(mWin, "DESTROY PENDING", true); } - mPendingDestroySurface.destroyInTransaction(); + mPendingDestroySurface.destroyNotInTransaction(); // Don't hide wallpaper if we're destroying a deferred surface // after a surface mode change. if (!mDestroyPreservedSurfaceUponRedraw) { @@ -683,8 +679,8 @@ class WindowStateAnimator { // WindowState.prepareSurfaces expands for surface insets (in order they don't get // clipped by the WindowState surface), so we need to go into the other direction here. - tmpMatrix.postTranslate(mWin.mAttrs.surfaceInsets.left, - mWin.mAttrs.surfaceInsets.top); + tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left, + mWin.mYOffset + mWin.mAttrs.surfaceInsets.top); // "convert" it into SurfaceFlinger's format @@ -699,6 +695,9 @@ class WindowStateAnimator { mDtDx = tmpFloats[Matrix.MSKEW_Y]; mDtDy = tmpFloats[Matrix.MSKEW_X]; mDsDy = tmpFloats[Matrix.MSCALE_Y]; + float x = tmpFloats[Matrix.MTRANS_X]; + float y = tmpFloats[Matrix.MTRANS_Y]; + mWin.mShownPosition.set(Math.round(x), Math.round(y)); // Now set the alpha... but because our current hardware // can't do alpha transformation on a non-opaque surface, @@ -708,7 +707,8 @@ class WindowStateAnimator { mShownAlpha = mAlpha; if (!mService.mLimitedAlphaCompositing || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format) - || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)))) { + || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy) + && x == frame.left && y == frame.top))) { //Slog.i(TAG_WM, "Applying alpha transform"); if (screenAnimation) { mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha(); @@ -738,6 +738,10 @@ class WindowStateAnimator { TAG, "computeShownFrameLocked: " + this + " not attached, mAlpha=" + mAlpha); + // WindowState.prepareSurfaces expands for surface insets (in order they don't get + // clipped by the WindowState surface), so we need to go into the other direction here. + mWin.mShownPosition.set(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left, + mWin.mYOffset + mWin.mAttrs.surfaceInsets.top); mShownAlpha = mAlpha; mHaveMatrix = false; mDsDx = mWin.mGlobalScale; @@ -788,6 +792,12 @@ class WindowStateAnimator { if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect + " fullscreen=" + fullscreen); + if (isFreeformResizing && !w.isChildWindow()) { + // For freeform resizing non child windows, we are using the big surface positioned + // at 0,0. Thus we must express the crop in that coordinate space. + clipRect.offset(w.mShownPosition.x, w.mShownPosition.y); + } + w.expandForSurfaceInsets(clipRect); // The clip rect was generated assuming (0,0) as the window origin, @@ -829,7 +839,7 @@ class WindowStateAnimator { return; } - mTmpSize.set(0, 0, 0, 0); + mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0); calculateSurfaceBounds(w, attrs); mExtraHScale = (float) 1.0; @@ -963,6 +973,11 @@ class WindowStateAnimator { // then take over the scaling until the new buffer arrives, and things // will be seamless. mForceScaleUntilResize = true; + } else { + if (!w.mSeamlesslyRotated) { + mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, + recoveringMemory); + } } // If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE @@ -1154,26 +1169,24 @@ class WindowStateAnimator { mSurfaceController.setTransparentRegionHint(region); } - boolean setWallpaperOffset(int dx, int dy) { - if (mXOffset == dx && mYOffset == dy) { - return false; - } - mXOffset = dx; - mYOffset = dy; + void setWallpaperOffset(Point shownPosition) { + final LayoutParams attrs = mWin.getAttrs(); + final int left = shownPosition.x - attrs.surfaceInsets.left; + final int top = shownPosition.y - attrs.surfaceInsets.top; try { if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset"); mService.openSurfaceTransaction(); - mSurfaceController.setPositionInTransaction(dx, dy, false); + mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left, + mWin.mFrame.top + top, false); applyCrop(null, false); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + mWin - + " pos=(" + dx + "," + dy + ")", e); + + " pos=(" + left + "," + top + ")", e); } finally { mService.closeSurfaceTransaction("setWallpaperOffset"); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setWallpaperOffset"); - return true; } } @@ -1408,7 +1421,7 @@ class WindowStateAnimator { void destroySurface() { try { if (mSurfaceController != null) { - mSurfaceController.destroyInTransaction(); + mSurfaceController.destroyNotInTransaction(); } } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying surface " + this diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 2f38556efa7c..554a60023aff 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -168,7 +168,7 @@ class WindowSurfaceController { } } - void destroyInTransaction() { + void destroyNotInTransaction() { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(8)); } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 7540e26c4e8d..5e003ff775fe 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -24,6 +24,7 @@ cc_library_static { "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", 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 000000000000..b53ea925e837 --- /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 07ddb0561f92..bf2a637cf54e 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -42,6 +42,7 @@ int register_android_server_VibratorService(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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/media/java/android/media/update/PlaybackInfoProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java index 36eb58a97df9..a20758e4b2da 100644 --- a/media/java/android/media/update/PlaybackInfoProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/CryptoTestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 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,18 +14,17 @@ * limitations under the License. */ -package android.media.update; +package com.android.server.devicepolicy; -import android.media.AudioAttributes; +import android.app.admin.SecurityLog; /** - * @hide + * Helper to call native BoringSSL self test. */ -// TODO(jaewan): @SystemApi -public interface PlaybackInfoProvider { - int getPlaybackType_impl(); - AudioAttributes getAudioAttributes_impl(); - int getControlType_impl(); - int getMaxVolume_impl(); - int getCurrentVolume_impl(); +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 60f204dd4bbc..0c0ce8dd5174 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 @@ public class DeviceAdminServiceController { } } + @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 953a79f625e6..875334413b5a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2044,6 +2044,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() { return new TransferOwnershipMetadataManager(); } + + public void runCryptoSelfTest() { + CryptoTestHelper.runAndLogSelfTest(); + } } /** @@ -2296,6 +2300,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (hasDeviceOwner && mInjector.securityLogGetLoggingEnabledProperty()) { mSecurityLogMonitor.start(); + mInjector.runCryptoSelfTest(); maybePauseDeviceWideLoggingLocked(); } } @@ -10221,6 +10226,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.registerContentObserver(mDefaultImeChanged, false, this, UserHandle.USER_ALL); } + @GuardedBy("DevicePolicyManagerService.this") private void addPendingChangeByOwnerLocked(int userId) { mUserIdsWithPendingChangesByOwner.add(userId); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3210f1a2e8b9..5ea113b801b5 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -65,7 +65,6 @@ import com.android.server.am.ActivityManagerService; import com.android.server.audio.AudioService; import com.android.server.broadcastradio.BroadcastRadioService; import com.android.server.camera.CameraServiceProxy; -import com.android.server.car.CarServiceHelperService; import com.android.server.clipboard.ClipboardService; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.coverage.CoverageService; @@ -220,6 +219,8 @@ public final class SystemServer { "com.google.android.things.services.IoTSystemService"; private static final String SLICE_MANAGER_SERVICE_CLASS = "com.android.server.slice.SliceManagerService$Lifecycle"; + private static final String CAR_SERVICE_HELPER_SERVICE_CLASS = + "com.android.internal.car.CarServiceHelperService"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; @@ -723,7 +724,6 @@ public final class SystemServer { 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", @@ -1104,18 +1104,12 @@ public final class SystemServer { 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 +1744,7 @@ public final class SystemServer { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { traceBeginAndSlog("StartCarServiceHelperService"); - mSystemServiceManager.startService(CarServiceHelperService.class); + mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS); traceEnd(); } diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 7d9736ed3fe5..d19043223416 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -1068,6 +1068,7 @@ public class ApfFilter { 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 49a1e79fd71e..8fbc01ea4493 100644 --- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java +++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java @@ -268,6 +268,7 @@ public class RouterAdvertisementDaemon { 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/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index ba5dde04897f..c1c32c28cb6b 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 @@ final class RemotePrintSpooler { } } + @GuardedBy("mLock") private void bindLocked() throws TimeoutException, InterruptedException { while (mIsBinding) { mLock.wait(); diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 5d8aca195ef8..97d6c43e2fd0 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/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 00e27c9a54cb..ab0bfefbbd4d 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 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi 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 ce5ee138cc3d..e40e3a42ee53 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 @@ public class KeySyncTaskTest { } @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 @@ public class KeySyncTaskTest { } @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 f9ffccdd5d38..0f0e3f386fec 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -255,6 +255,9 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); @@ -264,6 +267,56 @@ public class PlatformKeyManagerTest { } @Test + public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); // new version is available + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); + + verify(mKeyStoreProxy).containsAlias( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + any()); + } + + @Test + public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); + + verify(mKeyStoreProxy).containsAlias( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + any()); + } + + @Test public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), @@ -274,7 +327,13 @@ public class PlatformKeyManagerTest { + "platform/42/1/encrypt")).thenReturn(true); when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/2/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); @@ -295,11 +354,16 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed. - + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); // new version is available + + "platform/42/2/decrypt")).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); @@ -312,14 +376,70 @@ public class PlatformKeyManagerTest { } @Test - public void getEncryptKey_generatesNewKeyIfOldWasRemoved() throws Exception { + public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception { + doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + any()); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); // new version is available when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed. + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); + + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + any()); + } + @Test + public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(false); // was removed when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/2/encrypt")).thenReturn(true); // new version is available + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); + + mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); + + verify(mKeyStoreProxy).containsAlias( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + // Attempt to get regenerated key. + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + any()); + } + + @Test + public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(false); // was removed + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/2/decrypt")).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); @@ -332,10 +452,13 @@ public class PlatformKeyManagerTest { } @Test - public void getEncryptKey_getsEndryptKeyWithCorrectAlias() throws Exception { + public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/1/encrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 473a813c3838..a523b86f3b0b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -125,6 +125,7 @@ public class RecoverableKeyStoreManagerTest { private static final byte[] RECOVERY_RESPONSE_HEADER = "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final String TEST_ALIAS = "nick"; + private static final String TEST_ALIAS2 = "bob"; private static final int RECOVERABLE_KEY_SIZE_BYTES = 32; private static final int GENERATION_ID = 1; private static final byte[] NONCE = getUtf8Bytes("nonce"); @@ -237,15 +238,81 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void initRecoveryService_updatesShouldCreateSnapshot() throws Exception { + public void initRecoveryService_succeeds() throws Exception { + int uid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); + 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(); - // Sync is not needed. 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 @@ public class RecoverableKeyStoreManagerTest { } @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 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception { + public void recoverKeys_throwsIfFailedToDecryptAllApplicationKeys() throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, @@ -442,7 +489,7 @@ public class RecoverableKeyStoreManagerTest { keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); WrappedApplicationKey badApplicationKey = new WrappedApplicationKey( TEST_ALIAS, - randomBytes(32)); + encryptedApplicationKey(randomRecoveryKey(), randomBytes(32))); try { mRecoverableKeyStoreManager.recoverKeys( @@ -451,7 +498,7 @@ public class RecoverableKeyStoreManagerTest { /*applicationKeys=*/ ImmutableList.of(badApplicationKey)); fail("should have thrown"); } catch (ServiceSpecificException e) { - assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'"); + assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys"); } } @@ -487,6 +534,44 @@ public class RecoverableKeyStoreManagerTest { } @Test + public void recoverKeys_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyChainProtectionParams( + TYPE_LOCKSCREEN, + UI_FORMAT_PASSWORD, + KeyDerivationParams.createSha256Params(TEST_SALT), + TEST_SECRET))); + byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) + .getKeyClaimant(); + SecretKey recoveryKey = randomRecoveryKey(); + byte[] encryptedClaimResponse = encryptClaimResponse( + keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); + + byte[] applicationKeyBytes1 = randomBytes(32); + byte[] applicationKeyBytes2 = randomBytes(32); + + WrappedApplicationKey applicationKey1 = new WrappedApplicationKey( + TEST_ALIAS, + // Use a different recovery key here, so the decryption will fail + encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1)); + WrappedApplicationKey applicationKey2 = new WrappedApplicationKey( + TEST_ALIAS2, + encryptedApplicationKey(recoveryKey, applicationKeyBytes2)); + + Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + encryptedClaimResponse, + ImmutableList.of(applicationKey1, applicationKey2)); + + assertThat(recoveredKeys).hasSize(1); + assertThat(recoveredKeys.get(TEST_ALIAS2)).isEqualTo(applicationKeyBytes2); + } + + @Test public void setSnapshotCreatedPendingIntent() throws Exception { int uid = Binder.getCallingUid(); PendingIntent intent = PendingIntent.getBroadcast( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java new file mode 100644 index 000000000000..0e4f91b20ae2 --- /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/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java new file mode 100644 index 000000000000..37482a37003c --- /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 097d2141d9e0..1c5bcd498f5c 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.security.keystore.RecoveryController; 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 @@ public class RecoverableKeyStoreDbTest { 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 @@ public class RecoverableKeyStoreDbTest { } @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/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 0abb48f25041..98483a80e091 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 @@ public class PackageManagerSettingsTests { 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 @@ public class PackageManagerSettingsTests { 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 @@ public class PackageManagerSettingsTests { 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 5de393c7ae2b..a628b7b70c15 100644 --- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java +++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java @@ -79,6 +79,11 @@ public class FakeWindowState implements WindowManagerPolicy.WindowState { } @Override + public Point getShownPositionLw() { + return new Point(parentFrame.left, parentFrame.top); + } + + @Override public Rect getDisplayFrameLw() { return displayFrame; } diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 2284bbbbf3ac..63ac4af47207 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_BASE_APPLICATION; 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 org.junit.runner.RunWith; 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 f23bd6209614..3a1485e9fd46 100644 --- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.graphics.Color.BLUE; import static android.graphics.Color.RED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; @@ -27,7 +26,6 @@ import static android.view.Gravity.LEFT; import static android.view.Gravity.RIGHT; import static android.view.Gravity.TOP; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -49,6 +47,7 @@ import android.media.ImageReader; import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.Pair; @@ -66,18 +65,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.function.BooleanSupplier; /** * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests + * atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests */ // TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags. // TODO: Test non-Activity windows. @SmallTest -// TODO(b/68957554) -//@Presubmit +@Presubmit +@FlakyTest(bugId = 68957554) @RunWith(AndroidJUnit4.class) public class ScreenDecorWindowTests { @@ -123,40 +123,33 @@ public class ScreenDecorWindowTests { public void testScreenSides() throws Exception { // Decor on top final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); // Decor at the bottom updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness); // Decor to the left updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness); // Decor to the right updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetRight(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness); } @Test public void testMultipleDecors() throws Exception { // Test 2 decor windows on-top. createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mHalfDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness); createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); // And one at the bottom. createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness); - insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); - assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), mHalfDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness); } @Test @@ -164,18 +157,15 @@ public class ScreenDecorWindowTests { WindowInsets initialInsets = getInsets(mTestActivity); final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertEquals(mDecorThickness, insets.getSystemWindowInsetTop()); + assertTopInsetEquals(mTestActivity, mDecorThickness); updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, 0, PRIVATE_FLAG_IS_SCREEN_DECOR); - insets = getInsets(mTestActivity); - assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop()); + assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR); - insets = getInsets(mTestActivity); - assertEquals(mDecorThickness, insets.getSystemWindowInsetTop()); + assertTopInsetEquals(mTestActivity, mDecorThickness); } @Test @@ -183,17 +173,10 @@ public class ScreenDecorWindowTests { WindowInsets initialInsets = getInsets(mTestActivity); final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - WindowInsets insets = getInsets(mTestActivity); - assertGreaterOrEqual(insets.getSystemWindowInsetTop(), mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); removeWindow(decorWindow); - insets = getInsets(mTestActivity); - assertEquals(initialInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop()); - } - - private View createAppWindow() { - return createWindow("appWindow", TOP, MATCH_PARENT, MATCH_PARENT, BLUE, - FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR, 0); + assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); } private View createDecorWindow(int gravity, int width, int height) { @@ -249,10 +232,6 @@ public class ScreenDecorWindowTests { waitForIdle(); } - private WindowInsets getInsets(View v) { - return new WindowInsets(v.getRootWindowInsets()); - } - private WindowInsets getInsets(Activity a) { return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets()); } @@ -269,11 +248,59 @@ public class ScreenDecorWindowTests { lp.flags = (lp.flags & ~mask) | (flags & mask); } + /** + * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed. + */ + private void assertTopInsetEquals(Activity activity, int expected) throws Exception { + waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected); + assertEquals(expected, getInsets(activity).getSystemWindowInsetTop()); + } + + /** + * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected} + * waiting as needed. + */ + private void assertInsetGreaterOrEqual(Activity activity, int side, int expected) + throws Exception { + waitFor(() -> { + final WindowInsets insets = getInsets(activity); + switch (side) { + case TOP: return insets.getSystemWindowInsetTop() >= expected; + case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected; + case LEFT: return insets.getSystemWindowInsetLeft() >= expected; + case RIGHT: return insets.getSystemWindowInsetRight() >= expected; + default: return true; + } + }); + + final WindowInsets insets = getInsets(activity); + switch (side) { + case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break; + case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break; + case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break; + case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break; + } + } + /** Asserts that the first entry is greater than or equal to the second entry. */ private void assertGreaterOrEqual(int first, int second) throws Exception { Assert.assertTrue("Excepted " + first + " >= " + second, first >= second); } + private void waitFor(BooleanSupplier waitCondition) { + int retriesLeft = 5; + do { + if (waitCondition.getAsBoolean()) { + break; + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // Well I guess we are not waiting... + } + } while (retriesLeft-- > 0); + } + private void finishActivity(Activity a) { if (a == null) { return; diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index 6a4710bb06a4..4d41718e4da0 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; 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; @@ -57,15 +58,17 @@ public class WindowStateTests extends WindowTestsBase { 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 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 d6f61cd12765..7b2c040db5ab 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -188,6 +188,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { protected void reportSeen(NotificationRecord r) { return; } + + @Override + protected void reportUserInteraction(NotificationRecord r) { + return; + } } @Before diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index cc21199edfd9..32db7526f3c0 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 @@ public class AppStandbyController { 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 @@ public class AppStandbyController { 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 @@ public class AppStandbyController { } } + @GuardedBy("mAppIdleLock") @StandbyBuckets int getBucketForLocked(String packageName, int userId, long elapsedRealtime) { int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId, @@ -627,11 +634,11 @@ public class AppStandbyController { 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 @@ public class AppStandbyController { 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 @@ public class AppStandbyController { 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 @@ public class AppStandbyController { 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/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index 4b2d9b9adbbb..43f189b26dfa 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 @@ class UsbProfileGroupSettingsManager { * 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 @@ class UsbProfileGroupSettingsManager { } } + @GuardedBy("mLock") private void readSettingsLocked() { if (DEBUG) Slog.v(TAG, "readSettingsLocked()"); @@ -386,6 +388,7 @@ class UsbProfileGroupSettingsManager { * <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 @@ class UsbProfileGroupSettingsManager { return null; } + @GuardedBy("mLock") private boolean clearCompatibleMatchesLocked(@NonNull UserPackage userPackage, @NonNull DeviceFilter filter) { ArrayList<DeviceFilter> keysToRemove = new ArrayList<>(); @@ -892,6 +896,7 @@ class UsbProfileGroupSettingsManager { return !keysToRemove.isEmpty(); } + @GuardedBy("mLock") private boolean clearCompatibleMatchesLocked(@NonNull UserPackage userPackage, @NonNull AccessoryFilter filter) { ArrayList<AccessoryFilter> keysToRemove = new ArrayList<>(); @@ -915,6 +920,7 @@ class UsbProfileGroupSettingsManager { 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 1edc46967495..c1a75912425a 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 @@ public class UsbService extends IUsbManager.Stub { * * @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 6799417939f2..8c18518a6d67 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -352,8 +352,11 @@ public final class Call { */ 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 @@ public final class Call { 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 @@ public final class Call { } /** + * 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 e0b0bbf11544..24184e033c66 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -328,8 +328,11 @@ public abstract class Connection extends Conferenceable { */ 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 @@ public abstract class Connection extends Conferenceable { 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 @@ public abstract class Connection extends Conferenceable { /** * 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 c1040adc5163..211699ea5940 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -124,6 +124,7 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { } @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 @@ public abstract class ConnectionService extends Service { } 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 @@ public abstract class ConnectionService extends Service { 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 dcf5c271d0eb..1de67a5883e3 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -33,47 +33,48 @@ import java.util.Objects; 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 658685fe2907..8678e33f68b6 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 @@ public final class InCallAdapter { } /** + * 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 3a84f004462e..e35093c9656a 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 @@ oneway interface IConnectionService { 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 87ccd3ed4369..57df5c1e548e 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 @@ import android.telecom.PhoneAccountHandle; 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 7eb691be9d25..c8c898ad37cf 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1376,6 +1376,12 @@ public class CarrierConfigManager { */ 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 @@ public class CarrierConfigManager { 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 b362df9ff677..000000000000 --- a/telephony/java/android/telephony/LocationAccessPolicy.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.telephony; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Build; -import android.os.Process; -import android.os.Trace; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.util.SparseBooleanArray; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Helper for performing location access checks. - * @hide - */ -public final class LocationAccessPolicy { - /** - * API to determine if the caller has permissions to get cell location. - * - * @param pkgName Package name of the application requesting access - * @param uid The uid of the package - * @param pid The pid of the package - * @return boolean true or false if permissions is granted - */ - public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, - int uid, int pid) throws SecurityException { - Trace.beginSection("TelephonyLocationCheck"); - try { - // Always allow the phone process to access location. This avoid breaking legacy code - // that rely on public-facing APIs to access cell location, and it doesn't create a - // info leak risk because the cell location is stored in the phone process anyway. - if (uid == Process.PHONE_UID) { - return true; - } - - // We always require the location permission and also require the - // location mode to be on for non-legacy apps. Legacy apps are - // required to be in the foreground to at least mitigate the case - // where a legacy app the user is not using tracks their location. - // Granting ACCESS_FINE_LOCATION to an app automatically grants it - // ACCESS_COARSE_LOCATION. - - if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) == - PackageManager.PERMISSION_DENIED) { - return false; - } - final int opCode = AppOpsManager.permissionToOpCode( - Manifest.permission.ACCESS_COARSE_LOCATION); - if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) - .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { - return false; - } - if (!isLocationModeEnabled(context, UserHandle.getUserId(uid)) - && !isLegacyForeground(context, pkgName, uid)) { - return false; - } - // If the user or profile is current, permission is granted. - // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); - } finally { - Trace.endSection(); - } - } - - private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { - int locationMode = Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId); - return locationMode != Settings.Secure.LOCATION_MODE_OFF - && locationMode != Settings.Secure.LOCATION_MODE_SENSORS_ONLY; - } - - private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName, - int uid) { - long token = Binder.clearCallingIdentity(); - try { - return isLegacyVersion(context, pkgName) && isForegroundApp(context, uid); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) { - try { - if (context.getPackageManager().getApplicationInfo(pkgName, 0) - .targetSdkVersion <= Build.VERSION_CODES.O) { - return true; - } - } catch (PackageManager.NameNotFoundException e) { - // In case of exception, assume known app (more strict checking) - // Note: This case will never happen since checkPackage is - // called to verify validity before checking app's version. - } - return false; - } - - private static boolean isForegroundApp(@NonNull Context context, int uid) { - final ActivityManager am = context.getSystemService(ActivityManager.class); - return am.getUidImportance(uid) <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; - } - - private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { - return context.checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - == PackageManager.PERMISSION_GRANTED; - } - - private static boolean isCurrentProfile(@NonNull Context context, int uid) { - long token = Binder.clearCallingIdentity(); - try { - final int currentUser = ActivityManager.getCurrentUser(); - final int callingUserId = UserHandle.getUserId(uid); - if (callingUserId == currentUser) { - return true; - } else { - List<UserInfo> userProfiles = context.getSystemService( - UserManager.class).getProfiles(currentUser); - for (UserInfo user : userProfiles) { - if (user.id == callingUserId) { - return true; - } - } - } - return false; - } finally { - Binder.restoreCallingIdentity(token); - } - } -} diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 34f2dac028c7..debf43da79b4 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -612,9 +612,9 @@ public class SubscriptionManager { * onSubscriptionsChanged overridden. */ public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { - String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { - logd("register OnSubscriptionsChangedListener pkgName=" + pkgName + logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + " listener=" + listener); } try { @@ -623,7 +623,7 @@ public class SubscriptionManager { ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( "telephony.registry")); if (tr != null) { - tr.addOnSubscriptionsChangedListener(pkgName, listener.callback); + tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback); } } catch (RemoteException ex) { // Should not happen diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index 0b3cbad0d2e2..0c17147ca3fa 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -55,10 +55,11 @@ public class UiccSlotInfo implements Parcelable { /** 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 @@ public class UiccSlotInfo implements Parcelable { }; 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 class UiccSlotInfo implements Parcelable { } 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 @@ public class UiccSlotInfo implements Parcelable { } 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 c3d103f88db3..a20d4f5ad27b 100644 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -754,6 +754,22 @@ public class ImsCallSession { } /** + * 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 00cb1e25f66d..e5ed82572873 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 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { } /** + * 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 c6ca6fdb0fd8..7b9fe2b144ab 100644 --- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java @@ -174,6 +174,11 @@ public class ImsCallSessionImplBase implements AutoCloseable { } @Override + public void deflect(String deflectNumber) { + ImsCallSessionImplBase.this.deflect(deflectNumber); + } + + @Override public void reject(int reason) { ImsCallSessionImplBase.this.reject(reason); } @@ -395,6 +400,14 @@ public class ImsCallSessionImplBase implements AutoCloseable { } /** + * 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 203e6cf9c577..15234e5c0e92 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl @@ -136,6 +136,13 @@ interface IImsCallSession { 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 343d49fa077e..b65cda9302a7 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -24,6 +24,9 @@ java_library { srcs: ["src/**/*.java"], + // Needs to be consistent with the repackaged version of this make target. + java_version: "1.8", + no_framework_libs: true, hostdex: true, libs: [ @@ -55,13 +58,14 @@ java_library_static { name: "repackaged.android.test.base", static_libs: ["android.test.base"], - no_framework_libs: true, libs: [ "framework", ], jarjar_rules: "jarjar-rules.txt", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", } // Build the android.test.base-minus-junit library diff --git a/test-mock/Android.bp b/test-mock/Android.bp index b1ae40e17b9d..54e07a1673e7 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -19,6 +19,8 @@ java_library { name: "android.test.mock", + // Needs to be consistent with the repackaged version of this make target. + java_version: "1.8", srcs: ["src/**/*.java"], no_framework_libs: true, @@ -35,4 +37,6 @@ java_library_static { static_libs: ["android.test.mock"], jarjar_rules: "jarjar-rules.txt", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 1643a98b21b2..66b95271ee06 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -19,6 +19,8 @@ java_library { name: "android.test.runner", + // Needs to be consistent with the repackaged version of this make target. + java_version: "1.8", srcs: ["src/**/*.java"], no_framework_libs: true, @@ -55,4 +57,6 @@ java_library_static { static_libs: ["android.test.runner"], jarjar_rules: "jarjar-rules.txt", + // Pin java_version until jarjar is certified to support later versions. http://b/72703434 + java_version: "1.8", } diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml index 2591aaf8f1a6..d62ef9ec210c 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/JankBench/Android.mk b/tests/JankBench/Android.mk index 12568a09e71e..291ba78758b4 100644 --- a/tests/JankBench/Android.mk +++ b/tests/JankBench/Android.mk @@ -19,7 +19,7 @@ LOCAL_RESOURCE_DIR := \ 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 c6824ecea976..8697f1b085bf 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 60327e5cb2a4..c8e6c2091d8f 100644 --- a/tests/UiBench/Android.mk +++ b/tests/UiBench/Android.mk @@ -15,7 +15,7 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res 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 9aad413c354b..04266c5b3a0f 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -172,7 +172,7 @@ public class MacAddressTest { final int iterations = 1000; final String expectedAndroidOui = "da:a1:19"; for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(); + MacAddress mac = MacAddress.createRandomUnicastAddressWithGoogleBase(); String stringRepr = mac.toString(); assertTrue(stringRepr + " expected to be a locally assigned address", @@ -195,6 +195,15 @@ public class MacAddressTest { assertTrue(stringRepr + " expected to begin with " + expectedLocalOui, stringRepr.startsWith(expectedLocalOui)); } + + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddress.createRandomUnicastAddress(); + String stringRepr = mac.toString(); + + assertTrue(stringRepr + " expected to be a locally assigned address", + mac.isLocallyAssigned()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); + } } @Test diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index c9e272c32eae..1afd283a430a 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -1446,6 +1446,13 @@ class LinkCommand { 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 f278aec8eb6f..d75aea507980 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.sdkparcelables import org.objectweb.asm.ClassVisitor @@ -25,4 +41,4 @@ class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) super.visit(version, access, name, signature, superName, interfaces) } -}
\ No newline at end of file +} diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt index 3e9d92cd978f..22e8d781335b 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.sdkparcelables import org.objectweb.asm.ClassReader @@ -53,4 +69,4 @@ fun main(args: Array<String>) { fun usage() { System.err.println("Usage: <input jar> <output aidl>") kotlin.system.exitProcess(1) -}
\ No newline at end of file +} diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt index 620f798daf48..d6a0a4516a6d 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.sdkparcelables /** A class that uses an ancestor map to find all classes that diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt index edfc8259a738..c9bcbc9cadcf 100644 --- a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt +++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.sdkparcelables import junit.framework.TestCase.assertEquals diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java index fe63aa1bf79b..bdbc149a0a42 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.RangingRequest; 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 @@ import java.util.List; /** @hide */ @SystemApi +@Deprecated @SystemService(Context.WIFI_RTT_SERVICE) public class RttManager { @@ -181,6 +182,7 @@ public class RttManager { /** * 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 @@ public class RttManager { }; } + /** + * 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 @@ public class RttManager { } /** pseudo-private class used to parcel arguments */ + @Deprecated public static class ParcelableRttParams implements Parcelable { @NonNull @@ -589,12 +596,14 @@ public class RttManager { }; } + @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 @@ public class RttManager { /** pseudo-private class used to parcel results. */ + @Deprecated public static class ParcelableRttResults implements Parcelable { public RttResult mResults[]; @@ -838,8 +848,8 @@ public class RttManager { } 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 @@ public class RttManager { }; } - + @Deprecated public static interface RttListener { public void onSuccess(RttResult[] results); public void onFailure(int reason, String description); @@ -919,52 +929,10 @@ public class RttManager { } /** - * 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 @@ public class RttManager { } } + /** + * 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 @@ public class RttManager { * 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 @@ public class RttManager { * 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 @@ public class RttManager { * <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 @@ public class RttManager { * * @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 @@ public class RttManager { /** @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 @@ public class RttManager { * 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 c46789cab50e..8024bf08b1d4 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -273,27 +273,6 @@ public class ScanResult implements Parcelable { 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 7da2656761c1..ddcf327b9dd1 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -917,6 +917,9 @@ public class WifiConfiguration implements Parcelable { * 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 @@ public class WifiConfiguration implements Parcelable { 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 32f21b9cbc79..52b3d8691c79 100644 --- a/wifi/java/android/net/wifi/rtt/RangingRequest.java +++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java @@ -156,14 +156,18 @@ public final class RangingRequest implements Parcelable { /** * 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 @@ public final class RangingRequest implements Parcelable { /** * 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 e7377c169ec4..8a3a7f5a2d79 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -176,6 +176,8 @@ public class WifiConfigurationTest { @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 @@ public class WifiConfigurationTest { @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(); |