Merge "[Magnifier - 5] Auto invalidate and misc"
diff --git a/Android.mk b/Android.mk
index b5efd47..c87aec4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -425,7 +425,6 @@
core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl \
core/java/com/android/internal/widget/ILockSettings.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
- core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
keystore/java/android/security/IKeyChainAliasCallback.aidl \
keystore/java/android/security/IKeyChainService.aidl \
location/java/android/location/IBatchedLocationCallback.aidl \
@@ -570,6 +569,7 @@
../../system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl \
LOCAL_SRC_FILES += \
+ ../../system/core/storaged/binder/android/os/IStoraged.aidl \
../../system/netd/server/binder/android/net/INetd.aidl \
../../system/vold/binder/android/os/IVold.aidl \
../../system/vold/binder/android/os/IVoldListener.aidl \
@@ -578,6 +578,8 @@
LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
+LOCAL_AIDL_INCLUDES += core/java/android/os/StatsLogEventWrapper.aidl
+
LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
LOCAL_SRC_FILES += \
lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl \
@@ -608,6 +610,7 @@
frameworks/av/drm/libmediadrm/aidl \
frameworks/av/media/libaudioclient/aidl \
frameworks/native/aidl/gui \
+ system/core/storaged/binder \
system/netd/server/binder \
system/vold/binder \
system/bt/binder
@@ -648,6 +651,8 @@
LOCAL_MODULE := framework
+LOCAL_JAVAC_SHARD_SIZE := 150
+
LOCAL_DX_FLAGS := --core-library --multi-dex
LOCAL_JACK_FLAGS := --multi-dex native
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 200f92f..f08b402 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -4,11 +4,14 @@
LOCAL_MODULE_TAGS := tests
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ src/android/os/ISomeService.aidl
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
apct-perftests-utils \
+ guava \
legacy-android-test
LOCAL_PACKAGE_NAME := CorePerfTests
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index a709ee3..f27e8c8 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -5,6 +5,7 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.StubActivity" />
+ <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/core/src/android/os/ISomeService.aidl b/apct-tests/perftests/core/src/android/os/ISomeService.aidl
new file mode 100644
index 0000000..0658b69
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/ISomeService.aidl
@@ -0,0 +1,5 @@
+package android.os;
+
+interface ISomeService {
+ void readDisk(int times);
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/os/SomeService.java b/apct-tests/perftests/core/src/android/os/SomeService.java
new file mode 100644
index 0000000..bdfaa44
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/SomeService.java
@@ -0,0 +1,42 @@
+package android.os;
+
+import android.app.Service;
+import android.content.Intent;
+import java.io.File;
+import java.io.IOException;
+
+/** Service in separate process available for calling over binder. */
+public class SomeService extends Service {
+
+ private File mTempFile;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ try {
+ mTempFile = File.createTempFile("foo", "bar");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final ISomeService.Stub mBinder =
+ new ISomeService.Stub() {
+ public void readDisk(int times) {
+ for (int i = 0; i < times; i++) {
+ mTempFile.exists();
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mTempFile.delete();
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/os/StrictModeTest.java b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
new file mode 100644
index 0000000..d973c20
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.os;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import com.google.common.util.concurrent.SettableFuture;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class StrictModeTest {
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void timeVmViolation() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+ causeVmViolations(state);
+ }
+
+ @Test
+ public void timeVmViolationNoStrictMode() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ causeVmViolations(state);
+ }
+
+ private static void causeVmViolations(BenchmarkState state) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(Uri.parse("content://com.example/foobar"), "image/jpeg");
+ final Context context = InstrumentationRegistry.getTargetContext();
+ while (state.keepRunning()) {
+ context.startActivity(intent);
+ }
+ }
+
+ @Test
+ public void timeThreadViolation() throws IOException {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ causeThreadViolations(state);
+ }
+
+ @Test
+ public void timeThreadViolationNoStrictMode() throws IOException {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ causeThreadViolations(state);
+ }
+
+ private static void causeThreadViolations(BenchmarkState state) throws IOException {
+ final File test = File.createTempFile("foo", "bar");
+ while (state.keepRunning()) {
+ test.exists();
+ }
+ test.delete();
+ }
+
+ @Test
+ public void timeCrossBinderThreadViolation() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ causeCrossProcessThreadViolations(state);
+ }
+
+ @Test
+ public void timeCrossBinderThreadViolationNoStrictMode() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ causeCrossProcessThreadViolations(state);
+ }
+
+ private static void causeCrossProcessThreadViolations(BenchmarkState state)
+ throws ExecutionException, InterruptedException, RemoteException {
+ final Context context = InstrumentationRegistry.getTargetContext();
+
+ SettableFuture<IBinder> binder = SettableFuture.create();
+ ServiceConnection connection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ binder.set(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ binder.set(null);
+ }
+ };
+ context.bindService(
+ new Intent(context, SomeService.class), connection, Context.BIND_AUTO_CREATE);
+ ISomeService someService = ISomeService.Stub.asInterface(binder.get());
+ while (state.keepRunning()) {
+ // Violate strictmode heavily.
+ someService.readDisk(10);
+ }
+ context.unbindService(connection);
+ }
+}
diff --git a/api/current.txt b/api/current.txt
index 87fa407..df8b7ef 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -25754,6 +25754,7 @@
method public java.lang.String getName();
method public int getTruncationLengthBits();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -25801,6 +25802,7 @@
public static class IpSecTransform.Builder {
ctor public IpSecTransform.Builder(android.content.Context);
method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
@@ -35072,6 +35074,7 @@
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+ field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37281,10 +37284,15 @@
}
public static final class FillEventHistory.Event {
+ method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
method public android.os.Bundle getClientState();
method public java.lang.String getDatasetId();
+ method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+ method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+ method public java.util.Set<java.lang.String> getSelectedDatasetIds();
method public int getType();
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+ field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -37305,6 +37313,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
public static final class FillResponse.Builder {
@@ -37313,6 +37322,7 @@
method public android.service.autofill.FillResponse build();
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setFlags(int);
method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -47119,9 +47129,9 @@
method public abstract void setInputType(int);
method public abstract void setLocaleList(android.os.LocaleList);
method public abstract void setLongClickable(boolean);
- method public abstract void setMaxTextEms(int);
- method public abstract void setMaxTextLength(int);
- method public abstract void setMinTextEms(int);
+ method public void setMaxTextEms(int);
+ method public void setMaxTextLength(int);
+ method public void setMinTextEms(int);
method public abstract void setOpaque(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
@@ -52060,6 +52070,7 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public void invalidate(int, int, int, int);
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
diff --git a/api/system-current.txt b/api/system-current.txt
index a868549..007316f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -27996,6 +27996,7 @@
method public java.lang.String getName();
method public int getTruncationLengthBits();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -28043,6 +28044,7 @@
public static class IpSecTransform.Builder {
ctor public IpSecTransform.Builder(android.content.Context);
method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
@@ -38136,6 +38138,7 @@
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+ field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -40383,10 +40386,15 @@
}
public static final class FillEventHistory.Event {
+ method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
method public android.os.Bundle getClientState();
method public java.lang.String getDatasetId();
+ method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+ method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+ method public java.util.Set<java.lang.String> getSelectedDatasetIds();
method public int getType();
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+ field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -40407,6 +40415,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
public static final class FillResponse.Builder {
@@ -40415,6 +40424,7 @@
method public android.service.autofill.FillResponse build();
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setFlags(int);
method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -41092,6 +41102,7 @@
method public android.os.IBinder onBind(android.content.Intent);
method public abstract java.util.List<android.service.settings.suggestions.Suggestion> onGetSuggestions();
method public abstract void onSuggestionDismissed(android.service.settings.suggestions.Suggestion);
+ method public abstract void onSuggestionLaunched(android.service.settings.suggestions.Suggestion);
}
}
@@ -50851,9 +50862,9 @@
method public abstract void setInputType(int);
method public abstract void setLocaleList(android.os.LocaleList);
method public abstract void setLongClickable(boolean);
- method public abstract void setMaxTextEms(int);
- method public abstract void setMaxTextLength(int);
- method public abstract void setMinTextEms(int);
+ method public void setMaxTextEms(int);
+ method public void setMaxTextLength(int);
+ method public void setMinTextEms(int);
method public abstract void setOpaque(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
@@ -56163,6 +56174,7 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public void invalidate(int, int, int, int);
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
diff --git a/api/test-current.txt b/api/test-current.txt
index c93722f..faccb6b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -25954,6 +25954,7 @@
method public java.lang.String getName();
method public int getTruncationLengthBits();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -26001,6 +26002,7 @@
public static class IpSecTransform.Builder {
ctor public IpSecTransform.Builder(android.content.Context);
method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
@@ -35343,6 +35345,7 @@
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+ field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37573,10 +37576,15 @@
}
public static final class FillEventHistory.Event {
+ method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
method public android.os.Bundle getClientState();
method public java.lang.String getDatasetId();
+ method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+ method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+ method public java.util.Set<java.lang.String> getSelectedDatasetIds();
method public int getType();
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+ field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -37597,6 +37605,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
public static final class FillResponse.Builder {
@@ -37605,6 +37614,7 @@
method public android.service.autofill.FillResponse build();
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setFlags(int);
method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -47713,9 +47723,9 @@
method public abstract void setInputType(int);
method public abstract void setLocaleList(android.os.LocaleList);
method public abstract void setLongClickable(boolean);
- method public abstract void setMaxTextEms(int);
- method public abstract void setMaxTextLength(int);
- method public abstract void setMinTextEms(int);
+ method public void setMaxTextEms(int);
+ method public void setMaxTextLength(int);
+ method public void setMinTextEms(int);
method public abstract void setOpaque(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
@@ -52668,6 +52678,7 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public void invalidate(int, int, int, int);
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 38dcc62..8505d4c 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -24,6 +24,7 @@
src/condition/CombinationConditionTracker.cpp \
src/condition/condition_util.cpp \
src/condition/SimpleConditionTracker.cpp \
+ src/condition/ConditionWizard.cpp \
src/config/ConfigKey.cpp \
src/config/ConfigListener.cpp \
src/config/ConfigManager.cpp \
@@ -37,6 +38,7 @@
src/matchers/SimpleLogMatchingTracker.cpp \
src/metrics/CountAnomalyTracker.cpp \
src/metrics/CountMetricProducer.cpp \
+ src/metrics/DurationMetricProducer.cpp \
src/metrics/MetricsManager.cpp \
src/metrics/metrics_manager_util.cpp \
src/packages/UidMap.cpp \
@@ -47,7 +49,8 @@
src/stats_util.cpp
statsd_common_c_includes := \
- $(LOCAL_PATH)/src
+ $(LOCAL_PATH)/src \
+ $(LOCAL_PATH)/../../libs/services/include
statsd_common_aidl_includes := \
$(LOCAL_PATH)/../../core/java
@@ -93,7 +96,7 @@
endif
LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
-LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes)
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
LOCAL_C_INCLUDES += $(statsd_common_c_includes)
LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
@@ -115,7 +118,7 @@
LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_MODULE_TAGS := tests
-LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes)
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
LOCAL_C_INCLUDES += $(statsd_common_c_includes)
LOCAL_CFLAGS += \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 3def13f..e7825cf 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -73,6 +73,16 @@
}
}
+vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+ auto it = mMetricsManagers.find(key);
+ if (it == mMetricsManagers.end()) {
+ ALOGW("Config source %s does not exist", key.ToString().c_str());
+ return vector<StatsLogReport>();
+ }
+
+ return it->second->onDumpReport();
+}
+
void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
auto it = mMetricsManagers.find(key);
if (it != mMetricsManagers.end()) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index dc60485..3cefd29 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -33,7 +33,7 @@
class StatsLogProcessor : public ConfigListener {
public:
- StatsLogProcessor(const sp<UidMap> &uidMap);
+ StatsLogProcessor(const sp<UidMap>& uidMap);
virtual ~StatsLogProcessor();
virtual void OnLogEvent(const LogEvent& event);
@@ -41,6 +41,9 @@
void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
void OnConfigRemoved(const ConfigKey& key);
+ // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
+ std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+
private:
// TODO: use EventMetrics to log the events.
DropboxWriter m_dropbox_writer;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index b72e04e..1faeee0 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -63,10 +63,9 @@
// ======================================================================
StatsService::StatsService(const sp<Looper>& handlerLooper)
- : mStatsPullerManager(),
-
- mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better
+ : mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better
{
+ mStatsPullerManager = new StatsPullerManager();
mUidMap = new UidMap();
mConfigManager = new ConfigManager();
mProcessor = new StatsLogProcessor(mUidMap);
@@ -89,7 +88,7 @@
void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value,
uint32_t serial) {
- if (0 == strcmp("eng", value)) {
+ if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) {
reinterpret_cast<StatsService*>(cookie)->mEngBuild = true;
}
}
@@ -187,10 +186,17 @@
return cmd_print_stats_log(out, args);
}
- // adb shell cmd stats print-stats-log
if (!args[0].compare(String8("print-uid-map"))) {
return cmd_print_uid_map(out);
}
+
+ if (!args[0].compare(String8("dump-report"))) {
+ return cmd_dump_report(out, err, args);
+ }
+
+ if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
+ return cmd_print_pulled_metrics(out, args);
+ }
}
print_cmd_help(out);
@@ -208,6 +214,11 @@
fprintf(out, " Prints the UID, app name, version mapping.\n");
fprintf(out, "\n");
fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmds stats pull-source [int] \n");
+ fprintf(out, "\n");
+ fprintf(out, " Prints the output of a pulled metrics source (int indicates source)\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n");
fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
fprintf(out, "\n");
@@ -248,7 +259,9 @@
}
}
} else {
- fprintf(err, "The config can only be set for other UIDs on eng builds.\n");
+ fprintf(err,
+ "The config can only be set for other UIDs on eng or userdebug "
+ "builds.\n");
}
}
@@ -287,6 +300,54 @@
return UNKNOWN_ERROR;
}
+status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args) {
+ if (mProcessor != nullptr) {
+ const int argCount = args.size();
+ bool good = false;
+ int uid;
+ string name;
+ if (argCount == 2) {
+ // Automatically pick the UID
+ uid = IPCThreadState::self()->getCallingUid();
+ // TODO: What if this isn't a binder call? Should we fail?
+ name.assign(args[2].c_str(), args[2].size());
+ good = true;
+ } else if (argCount == 3) {
+ // If it's a userdebug or eng build, then the shell user can
+ // impersonate other uids.
+ if (mEngBuild) {
+ const char* s = args[1].c_str();
+ if (*s != '\0') {
+ char* end = NULL;
+ uid = strtol(s, &end, 0);
+ if (*end == '\0') {
+ name.assign(args[2].c_str(), args[2].size());
+ good = true;
+ }
+ }
+ } else {
+ fprintf(out,
+ "The metrics can only be dumped for other UIDs on eng or userdebug "
+ "builds.\n");
+ }
+ }
+ if (good) {
+ mProcessor->onDumpReport(ConfigKey(uid, name));
+ // TODO: print the returned StatsLogReport to file instead of printing to logcat.
+ fprintf(out, "Dump report for Config [%d,%s]\n", uid, name.c_str());
+ fprintf(out, "See the StatsLogReport in logcat...\n");
+ return android::OK;
+ } else {
+ // If arg parsing failed, print the help text and return an error.
+ print_cmd_help(out);
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ fprintf(out, "Log processor does not exist...\n");
+ return UNKNOWN_ERROR;
+ }
+}
+
status_t StatsService::cmd_print_stats_log(FILE* out, const Vector<String8>& args) {
long msec = 0;
@@ -301,6 +362,16 @@
return NO_ERROR;
}
+status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) {
+ int s = atoi(args[1].c_str());
+ auto stats = mStatsPullerManager->Pull(s);
+ for (const auto& it : stats) {
+ fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
+ }
+ fprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size());
+ return NO_ERROR;
+}
+
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
const vector<String16>& app) {
if (DEBUG) ALOGD("StatsService::informAllUidData was called");
@@ -362,10 +433,6 @@
if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
// TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
- String16 output = mStatsPullerManager.pull(StatsPullerManager::KERNEL_WAKELOCKS);
- // TODO: do something useful with the output instead of writing a string to screen.
- ALOGD("%s", String8(output).string());
- ALOGD("%d", int(output.size()));
return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 7d305e9..449a2b8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -111,11 +111,21 @@
status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
/**
+ * Print the event log.
+ */
+ status_t cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args);
+
+ /**
* Print the mapping of uids to package names.
*/
status_t cmd_print_uid_map(FILE* out);
/**
+ * Print contents of a pulled metrics source.
+ */
+ status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
+
+ /**
* Update a configuration.
*/
void set_config(int uid, const string& name, const StatsdConfig& config);
@@ -127,9 +137,8 @@
/**
* Fetches external metrics.
- * TODO: This should be an sp<>
*/
- StatsPullerManager mStatsPullerManager;
+ sp<StatsPullerManager> mStatsPullerManager;
/**
* Tracks the configurations that have been passed to statsd.
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 014747f..f56c15a 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -25,6 +25,7 @@
namespace os {
namespace statsd {
+using std::map;
using std::string;
using std::unique_ptr;
using std::unordered_map;
@@ -102,33 +103,62 @@
return true;
}
+void CombinationConditionTracker::isConditionMet(
+ const map<string, HashableDimensionKey>& conditionParameters,
+ const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+ for (const int childIndex : mChildren) {
+ if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+ allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
+ conditionCache);
+ }
+ }
+ conditionCache[mIndex] =
+ evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+}
+
bool CombinationConditionTracker::evaluateCondition(
const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
- std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) {
+ std::vector<ConditionState>& nonSlicedConditionCache,
+ std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
// value is up to date.
- if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+ if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
return false;
}
for (const int childIndex : mChildren) {
- if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+ if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
const sp<ConditionTracker>& child = mAllConditions[childIndex];
- child->evaluateCondition(event, eventMatcherValues, mAllConditions, conditionCache,
- changedCache);
+ child->evaluateCondition(event, eventMatcherValues, mAllConditions,
+ nonSlicedConditionCache, nonSlicedChangedCache,
+ slicedConditionChanged);
}
}
ConditionState newCondition =
- evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+ evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
- bool changed = (mConditionState != newCondition);
- mConditionState = newCondition;
+ bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+ mNonSlicedConditionState = newCondition;
- conditionCache[mIndex] = mConditionState;
+ nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
- changedCache[mIndex] = changed;
- return changed;
+ nonSlicedChangedCache[mIndex] = nonSlicedChanged;
+
+ if (mSliced) {
+ for (const int childIndex : mChildren) {
+ // If any of the sliced condition in children condition changes, the combination
+ // condition may be changed too.
+ if (slicedConditionChanged[childIndex]) {
+ slicedConditionChanged[mIndex] = true;
+ break;
+ }
+ }
+ ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
+ slicedConditionChanged[mIndex] == true);
+ }
+
+ return nonSlicedChanged;
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 5d2d77e..fc88a88 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -39,7 +39,14 @@
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) override;
+ std::vector<bool>& changedCache,
+ std::vector<bool>& slicedConditionMayChanged) override;
+
+ void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::vector<ConditionState>& conditionCache) override;
+
+ void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
private:
LogicalOperation mLogicalOperation;
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 8cc7e23..055b478 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -38,8 +38,9 @@
: mName(name),
mIndex(index),
mInitialized(false),
- mConditionState(ConditionState::kUnknown),
- mTrackerIndex(){};
+ mTrackerIndex(),
+ mNonSlicedConditionState(ConditionState::kUnknown),
+ mSliced(false){};
virtual ~ConditionTracker(){};
@@ -63,25 +64,47 @@
// event before ConditionTrackers, because ConditionTracker depends on
// LogMatchingTrackers.
// mAllConditions: the list of all ConditionTracker
- // conditionCache: the cached results of the ConditionTrackers for this new event.
- // changedCache: the bit map to record whether the condition has changed.
+ // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
+ // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
+ // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
+ // Because sliced condition needs parameters to determine the value. So the sliced
+ // condition is not pushed to metrics. We only inform the relevant metrics that the sliced
+ // condition may have changed, and metrics should pull the conditions that they are
+ // interested in.
virtual bool evaluateCondition(const LogEvent& event,
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) = 0;
+ std::vector<bool>& nonSlicedConditionChanged,
+ std::vector<bool>& slicedConditionMayChanged) = 0;
// Return the current condition state.
virtual ConditionState isConditionMet() {
- ALOGW("Condition %s value %d", mName.c_str(), mConditionState);
- return mConditionState;
+ return mNonSlicedConditionState;
};
+ // Query the condition with parameters.
+ // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the
+ // condition.
+ // [allConditions]: all condition trackers. This is needed because the condition evaluation is
+ // done recursively
+ // [conditionCache]: the cache holding the condition evaluation values.
+ virtual void isConditionMet(
+ const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::vector<ConditionState>& conditionCache) = 0;
+
// return the list of LogMatchingTracker index that this ConditionTracker uses.
virtual const std::set<int>& getLogTrackerIndex() const {
return mTrackerIndex;
}
+ virtual void setSliced(bool sliced) {
+ mSliced = mSliced | sliced;
+ }
+
+ virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
+
protected:
// We don't really need the string name, but having a name here makes log messages
// easy to debug.
@@ -93,11 +116,12 @@
// if it's properly initialized.
bool mInitialized;
- // current condition state.
- ConditionState mConditionState;
-
// the list of LogMatchingTracker index that this ConditionTracker uses.
std::set<int> mTrackerIndex;
+
+ ConditionState mNonSlicedConditionState;
+
+ bool mSliced;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
new file mode 100644
index 0000000..411f7e5
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 "ConditionWizard.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::string;
+using std::vector;
+
+ConditionState ConditionWizard::query(const int index,
+ const map<string, HashableDimensionKey>& parameters) {
+ vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
+
+ mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
+ return cache[index];
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
new file mode 100644
index 0000000..4889b64
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -0,0 +1,48 @@
+/*
+ * 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 CONDITION_WIZARD_H
+#define CONDITION_WIZARD_H
+
+#include "ConditionTracker.h"
+#include "condition_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
+class ConditionWizard : public virtual android::RefBase {
+public:
+ ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
+ : mAllConditions(conditionTrackers){};
+
+ // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
+ // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
+ // condition.
+ // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
+ // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
+ ConditionState query(const int conditionIndex,
+ const std::map<std::string, HashableDimensionKey>& conditionParameters);
+
+private:
+ std::vector<sp<ConditionTracker>>& mAllConditions;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#endif // CONDITION_WIZARD_H
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index fa583bf..bde3846 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -25,6 +25,7 @@
namespace os {
namespace statsd {
+using std::map;
using std::string;
using std::unique_ptr;
using std::unordered_map;
@@ -64,7 +65,7 @@
if (simpleCondition.has_stop_all()) {
auto pair = trackerNameIndexMap.find(simpleCondition.stop_all());
if (pair == trackerNameIndexMap.end()) {
- ALOGW("Stop matcher %s not found in the config", simpleCondition.stop().c_str());
+ ALOGW("Stop all matcher %s not found in the config", simpleCondition.stop().c_str());
return;
}
mStopAllLogMatcherIndex = pair->second;
@@ -89,41 +90,136 @@
return mInitialized;
}
+void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+ VLOG("%s DUMP:", name.c_str());
+
+ for (const auto& pair : conditions) {
+ VLOG("\t%s %d", pair.first.c_str(), pair.second);
+ }
+}
+
+void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
+ VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
+ mDimensionsList.push_back(keyMatchers);
+ mSliced = true;
+}
+
bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
const vector<MatchingState>& eventMatcherValues,
const vector<sp<ConditionTracker>>& mAllConditions,
vector<ConditionState>& conditionCache,
- vector<bool>& changedCache) {
+ vector<bool>& nonSlicedConditionChanged,
+ std::vector<bool>& slicedConditionChanged) {
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
// it has been evaluated.
- VLOG("Yes, already evaluated, %s %d", mName.c_str(), mConditionState);
+ VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
return false;
}
// Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
- ConditionState newCondition = mConditionState;
+
+ ConditionState newCondition = mNonSlicedConditionState;
+ bool matched = false;
// Note: The order to evaluate the following start, stop, stop_all matters.
// The priority of overwrite is stop_all > stop > start.
if (mStartLogMatcherIndex >= 0 &&
eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
+ matched = true;
newCondition = ConditionState::kTrue;
}
if (mStopLogMatcherIndex >= 0 &&
eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
+ matched = true;
newCondition = ConditionState::kFalse;
}
+ bool stopAll = false;
if (mStopAllLogMatcherIndex >= 0 &&
eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+ matched = true;
newCondition = ConditionState::kFalse;
+ stopAll = true;
}
- bool changed = (mConditionState != newCondition);
- mConditionState = newCondition;
- conditionCache[mIndex] = mConditionState;
- changedCache[mIndex] = changed;
- return changed;
+ if (matched == false) {
+ slicedConditionChanged[mIndex] = false;
+ nonSlicedConditionChanged[mIndex] = false;
+ conditionCache[mIndex] = mNonSlicedConditionState;
+ return false;
+ }
+
+ bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
+
+ bool slicedChanged = false;
+
+ if (stopAll) {
+ // TODO: handle stop all; all dimension should be cleared.
+ }
+
+ if (mDimensionsList.size() > 0) {
+ for (size_t i = 0; i < mDimensionsList.size(); i++) {
+ const auto& dim = mDimensionsList[i];
+ vector<KeyValuePair> key = getDimensionKey(event, dim);
+ HashableDimensionKey hashableKey = getHashableKey(key);
+ if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
+ mSlicedConditionState[hashableKey] != newCondition) {
+ slicedChanged = true;
+ mSlicedConditionState[hashableKey] = newCondition;
+ }
+ VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+ }
+ // dump all dimensions for debugging
+ if (DEBUG) {
+ print(mSlicedConditionState, mName);
+ }
+ }
+
+ // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
+ // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
+ // so mark the slicedConditionChanged to be true.
+ // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
+ // APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
+ // SCREEN_OFF is not sliced, and it changes from False -> True;
+ // We need to populate this change to parent condition. Because for App_B,
+ // the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
+ slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
+ nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
+
+ VLOG("SimpleCondition %s nonSlicedChange? %d SlicedChanged? %d", mName.c_str(),
+ nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
+ mNonSlicedConditionState = newCondition;
+ conditionCache[mIndex] = mNonSlicedConditionState;
+
+ return nonSlicedConditionChanged[mIndex];
+}
+
+void SimpleConditionTracker::isConditionMet(
+ const map<string, HashableDimensionKey>& conditionParameters,
+ const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+ const auto pair = conditionParameters.find(mName);
+ if (pair == conditionParameters.end()) {
+ // the query does not need my sliced condition. just return the non sliced condition.
+ conditionCache[mIndex] = mNonSlicedConditionState;
+ VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+ return;
+ }
+
+ const HashableDimensionKey& key = pair->second;
+ VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
+
+ if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
+ // never seen this key before. the condition is unknown to us.
+ conditionCache[mIndex] = ConditionState::kUnknown;
+ } else {
+ conditionCache[mIndex] = mSlicedConditionState[key];
+ }
+
+ VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
+
+ if (DEBUG) {
+ print(mSlicedConditionState, mName);
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 9dd06a1..1f357f0 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -19,6 +19,7 @@
#include "ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
namespace android {
namespace os {
@@ -26,6 +27,8 @@
class SimpleConditionTracker : public virtual ConditionTracker {
public:
+ // dimensions is a vector of vector because for one single condition, different metrics may be
+ // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
SimpleConditionTracker(const std::string& name, const int index,
const SimpleCondition& simpleCondition,
const std::unordered_map<std::string, int>& trackerNameIndexMap);
@@ -41,7 +44,14 @@
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) override;
+ std::vector<bool>& changedCache,
+ std::vector<bool>& slicedChangedCache) override;
+
+ void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::vector<ConditionState>& conditionCache) override;
+
+ void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
private:
// The index of the LogEventMatcher which defines the start.
@@ -55,6 +65,13 @@
// The index of the LogEventMatcher which defines the stop all.
int mStopAllLogMatcherIndex;
+
+ // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
+ std::vector<std::vector<KeyMatcher>> mDimensionsList;
+
+ // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+ // that StatsLogReport wants.
+ std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index c7c8fcc..40d41be 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -23,6 +23,7 @@
#include <log/logprint.h>
#include <utils/Errors.h>
#include <unordered_map>
+#include "../matchers/matcher_util.h"
#include "ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -90,6 +91,25 @@
return newCondition;
}
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+ const EventConditionLink& link) {
+ vector<KeyMatcher> eventKey;
+ eventKey.reserve(link.key_in_main().size());
+
+ for (const auto& key : link.key_in_main()) {
+ eventKey.push_back(key);
+ }
+
+ vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey);
+
+ for (int i = 0; i < link.key_in_main_size(); i++) {
+ auto& kv = dimensionKey[i];
+ kv.set_key(link.key_in_condition(i).key());
+ }
+
+ return getHashableKey(dimensionKey);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index a4fcea3..47e245e 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -18,6 +18,7 @@
#define CONDITION_UTIL_H
#include <vector>
+#include "../matchers/matcher_util.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -35,6 +36,9 @@
ConditionState evaluateCombinationCondition(const std::vector<int>& children,
const LogicalOperation& operation,
const std::vector<ConditionState>& conditionCache);
+
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+ const EventConditionLink& link);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 2a4d6e2..038edd3 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -118,130 +118,156 @@
StatsdConfig config;
config.set_config_id(12345L);
- // One count metric to count screen on
+ int WAKE_LOCK_TAG_ID = 11;
+ int WAKE_LOCK_UID_KEY_ID = 1;
+ int WAKE_LOCK_STATE_KEY = 2;
+ int WAKE_LOCK_ACQUIRE_VALUE = 1;
+ int WAKE_LOCK_RELEASE_VALUE = 0;
+
+ int APP_USAGE_ID = 12345;
+ int APP_USAGE_UID_KEY_ID = 1;
+ int APP_USAGE_STATE_KEY = 2;
+ int APP_USAGE_FOREGROUND = 1;
+ int APP_USAGE_BACKGROUND = 0;
+
+ int SCREEN_EVENT_TAG_ID = 2;
+ int SCREEN_EVENT_STATE_KEY = 1;
+ int SCREEN_EVENT_ON_VALUE = 2;
+ int SCREEN_EVENT_OFF_VALUE = 1;
+
+ int UID_PROCESS_STATE_TAG_ID = 3;
+ int UID_PROCESS_STATE_UID_KEY = 1;
+
+ // Count Screen ON events.
CountMetric* metric = config.add_count_metric();
- metric->set_metric_id(20150717L);
- metric->set_what("SCREEN_IS_ON");
+ metric->set_metric_id(1);
+ metric->set_what("SCREEN_TURNED_ON");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- // One count metric to count PHOTO_CHANGE_OR_CHROME_CRASH
+ // Count process state changes, slice by uid.
metric = config.add_count_metric();
- metric->set_metric_id(20150718L);
- metric->set_what("PHOTO_PROCESS_STATE_CHANGE");
- metric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
- metric->set_condition("SCREEN_IS_ON");
+ metric->set_metric_id(2);
+ metric->set_what("PROCESS_STATE_CHANGE");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ KeyMatcher* keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+ // Count process state changes, slice by uid, while SCREEN_IS_OFF
+ metric = config.add_count_metric();
+ metric->set_metric_id(3);
+ metric->set_what("PROCESS_STATE_CHANGE");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+ metric->set_condition("SCREEN_IS_OFF");
+
+ // Count wake lock, slice by uid, while SCREEN_IS_OFF and app in background
+ metric = config.add_count_metric();
+ metric->set_metric_id(4);
+ metric->set_what("APP_GET_WL");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+ metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ EventConditionLink* link = metric->add_links();
+ link->set_condition("APP_IS_BACKGROUND");
+ link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+ link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+ // Duration of an app holding wl, while screen on and app in background
+ DurationMetric* durationMetric = config.add_duration_metric();
+ durationMetric->set_metric_id(5);
+ durationMetric->set_start("APP_GET_WL");
+ durationMetric->set_stop("APP_RELEASE_WL");
+ durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+ keyMatcher = durationMetric->add_dimension();
+ keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+ durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ link = durationMetric->add_links();
+ link->set_condition("APP_IS_BACKGROUND");
+ link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+ link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+ // Event matchers............
LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
-
+ eventMatcher->set_name("SCREEN_TURNED_ON");
SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
- 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
- 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+ simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+ KeyValueMatcher* keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+ keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
eventMatcher = config.add_log_entry_matcher();
- eventMatcher->set_name("SCREEN_IS_OFF");
-
+ eventMatcher->set_name("SCREEN_TURNED_OFF");
simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
- 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
- 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+ simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+ keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
- LogEntryMatcher* procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_CRASH");
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("PROCESS_STATE_CHANGE");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
- SimpleLogEntryMatcher* simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
- simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
- KeyValueMatcher* keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
- keyValueMatcher->set_eq_string(
- "com.google.android.apps.photos" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_GOES_BACKGROUND");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+ keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- keyValueMatcher->set_eq_int(2);
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_GOES_FOREGROUND");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+ keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_START");
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_GET_WL");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+ keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
- simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
- simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
- keyValueMatcher->set_eq_string(
- "com.google.android.apps.photos" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_RELEASE_WL");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+ keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1 /*STATE*/);
- keyValueMatcher->set_eq_int(1);
-
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_PROCESS_STATE_CHANGE");
- LogEntryMatcher_Combination* combinationMatcher = procEventMatcher->mutable_combination();
- combinationMatcher->set_operation(LogicalOperation::OR);
- combinationMatcher->add_matcher("PHOTO_START");
- combinationMatcher->add_matcher("PHOTO_CRASH");
-
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("CHROME_CRASH");
-
- simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
- simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
- keyValueMatcher->set_eq_string(
- "com.android.chrome" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1 /*STATE*/);
- keyValueMatcher->set_eq_int(2);
-
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_CHANGE_OR_CHROME_CRASH");
- combinationMatcher = procEventMatcher->mutable_combination();
- combinationMatcher->set_operation(LogicalOperation::OR);
- combinationMatcher->add_matcher("PHOTO_PROCESS_STATE_CHANGE");
- combinationMatcher->add_matcher("CHROME_CRASH");
-
+ // Conditions.............
Condition* condition = config.add_condition();
condition->set_name("SCREEN_IS_ON");
SimpleCondition* simpleCondition = condition->mutable_simple_condition();
- simpleCondition->set_start("SCREEN_IS_ON");
- simpleCondition->set_stop("SCREEN_IS_OFF");
-
- condition = config.add_condition();
- condition->set_name("PHOTO_STARTED");
-
- simpleCondition = condition->mutable_simple_condition();
- simpleCondition->set_start("PHOTO_START");
- simpleCondition->set_stop("PHOTO_CRASH");
+ simpleCondition->set_start("SCREEN_TURNED_ON");
+ simpleCondition->set_stop("SCREEN_TURNED_OFF");
condition = config.add_condition();
condition->set_name("SCREEN_IS_OFF");
-
simpleCondition = condition->mutable_simple_condition();
- simpleCondition->set_start("SCREEN_IS_OFF");
- simpleCondition->set_stop("SCREEN_IS_ON");
+ simpleCondition->set_start("SCREEN_TURNED_OFF");
+ simpleCondition->set_stop("SCREEN_TURNED_ON");
condition = config.add_condition();
- condition->set_name("SCREEN_IS_EITHER_ON_OFF");
-
- Condition_Combination* combination = condition->mutable_combination();
- combination->set_operation(LogicalOperation::OR);
- combination->add_condition("SCREEN_IS_ON");
- combination->add_condition("SCREEN_IS_OFF");
+ condition->set_name("APP_IS_BACKGROUND");
+ simpleCondition = condition->mutable_simple_condition();
+ simpleCondition->set_start("APP_GOES_BACKGROUND");
+ simpleCondition->set_stop("APP_GOES_FOREGROUND");
condition = config.add_condition();
- condition->set_name("SCREEN_IS_NEITHER_ON_OFF");
-
- combination = condition->mutable_combination();
- combination->set_operation(LogicalOperation::NOR);
- combination->add_condition("SCREEN_IS_ON");
- combination->add_condition("SCREEN_IS_OFF");
+ condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ Condition_Combination* combination_condition = condition->mutable_combination();
+ combination_condition->set_operation(LogicalOperation::AND);
+ combination_condition->add_condition("APP_IS_BACKGROUND");
+ combination_condition->add_condition("SCREEN_IS_ON");
return config;
}
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.cpp b/cmds/statsd/src/external/KernelWakelockPuller.cpp
index b9abee0..ee072f8 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.cpp
+++ b/cmds/statsd/src/external/KernelWakelockPuller.cpp
@@ -37,9 +37,9 @@
// The reading and parsing are implemented in Java. It is not difficult to port over. But for now
// let StatsCompanionService handle that and send the data back.
-String16 KernelWakelockPuller::pull() {
+vector<StatsLogEventWrapper> KernelWakelockPuller::pull() {
sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
- String16 returned_value("");
+ vector<StatsLogEventWrapper> returned_value;
if (statsCompanion != NULL) {
Status status = statsCompanion->pullData(KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS,
&returned_value);
@@ -47,12 +47,10 @@
ALOGW("error pulling kernel wakelock");
}
ALOGD("KernelWakelockPuller::pull succeeded!");
- // TODO: remove this when we integrate into aggregation chain.
- ALOGD("%s", String8(returned_value).string());
return returned_value;
} else {
ALOGW("statsCompanion not found!");
- return String16();
+ return returned_value;
}
}
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.h b/cmds/statsd/src/external/KernelWakelockPuller.h
index 1ec3376..c12806c 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.h
+++ b/cmds/statsd/src/external/KernelWakelockPuller.h
@@ -29,7 +29,7 @@
// a number of stats need to be pulled from StatsCompanionService
//
const static int PULL_CODE_KERNEL_WAKELOCKS;
- String16 pull() override;
+ vector<StatsLogEventWrapper> pull() override;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 5e556b8..6655629 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -17,7 +17,12 @@
#ifndef STATSD_STATSPULLER_H
#define STATSD_STATSPULLER_H
+#include <android/os/StatsLogEventWrapper.h>
#include <utils/String16.h>
+#include <vector>
+
+using android::os::StatsLogEventWrapper;
+using std::vector;
namespace android {
namespace os {
@@ -26,8 +31,8 @@
class StatsPuller {
public:
virtual ~StatsPuller(){};
- // use string for now, until we figure out how to integrate into the aggregation path
- virtual String16 pull() = 0;
+
+ virtual vector<StatsLogEventWrapper> pull() = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 6e8d58bc..7f554d3 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -21,6 +21,11 @@
#include "KernelWakelockPuller.h"
#include "StatsService.h"
#include "external/StatsPullerManager.h"
+#include "logd/LogEvent.h"
+#include <cutils/log.h>
+#include <algorithm>
+
+#include <iostream>
using namespace android;
@@ -35,13 +40,27 @@
{static_cast<int>(KERNEL_WAKELOCKS), std::make_unique<KernelWakelockPuller>()});
}
-String16 StatsPullerManager::pull(int pullCode) {
+vector<std::shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode) {
if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
+
+ vector<std::shared_ptr<LogEvent>> ret;
if (mStatsPullers.find(pullCode) != mStatsPullers.end()) {
- return (mStatsPullers.find(pullCode)->second)->pull();
+ vector<StatsLogEventWrapper> outputs = (mStatsPullers.find(pullCode)->second)->pull();
+ for (const StatsLogEventWrapper& it : outputs) {
+ log_msg tmp;
+ tmp.entry_v1.len = it.bytes.size();
+ // Manually set the header size to 28 bytes to match the pushed log events.
+ tmp.entry.hdr_size = 28;
+ // And set the received bytes starting after the 28 bytes reserved for header.
+ std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + 28);
+ std::shared_ptr<LogEvent> evt = std::make_shared<LogEvent>(tmp);
+ ret.push_back(evt);
+ // ret.emplace_back(tmp);
+ }
+ return ret;
} else {
ALOGD("Unknown pull code %d", pullCode);
- return String16();
+ return ret; // Return early since we don't know what to pull.
}
}
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index f143424..e46aec1 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -20,6 +20,8 @@
#include <utils/String16.h>
#include <unordered_map>
#include "external/StatsPuller.h"
+#include "logd/LogEvent.h"
+#include "matchers/matcher_util.h"
namespace android {
namespace os {
@@ -27,7 +29,7 @@
const static int KERNEL_WAKELOCKS = 1;
-class StatsPullerManager {
+class StatsPullerManager : public virtual RefBase {
public:
// Enums of pulled data types (pullCodes)
// These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
@@ -35,7 +37,8 @@
const static int KERNEL_WAKELOCKS;
StatsPullerManager();
- String16 pull(const int pullCode);
+ // We return a vector of shared_ptr since LogEvent's copy constructor is not available.
+ vector<std::shared_ptr<LogEvent>> Pull(const int pullCode);
private:
std::unordered_map<int, std::unique_ptr<StatsPuller>> mStatsPullers;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 9fa2baf..fb992c1 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -23,29 +23,30 @@
namespace statsd {
using std::ostringstream;
+using std::string;
-LogEvent::LogEvent(const log_msg& msg) {
- init(msg);
+// We need to keep a copy of the android_log_event_list owned by this instance so that the char*
+// for strings is not cleared before we can read them.
+LogEvent::LogEvent(log_msg msg) : mList(msg) {
+ init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
}
-LogEvent::LogEvent(int64_t timestampNs, android_log_event_list* reader) {
- init(timestampNs, reader);
+LogEvent::LogEvent(int tag) : mList(tag) {
}
LogEvent::~LogEvent() {
}
+void LogEvent::init() {
+ mList.convert_to_reader();
+ init(mTimestampNs, &mList);
+}
+
/**
* The elements of each log event are stored as a vector of android_log_list_elements.
* The goal is to do as little preprocessing as possible, because we read a tiny fraction
* of the elements that are written to the log.
*/
-void LogEvent::init(const log_msg& msg) {
-
- android_log_event_list list(const_cast<log_msg&>(msg));
- init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &list);
-}
-
void LogEvent::init(int64_t timestampNs, android_log_event_list* reader) {
mTimestampNs = timestampNs;
mTagId = reader->tag();
@@ -79,6 +80,10 @@
} while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
}
+android_log_event_list* LogEvent::GetAndroidLogEventList() {
+ return &mList;
+}
+
int64_t LogEvent::GetLong(size_t key, status_t* err) const {
if (key < 1 || (key - 1) >= mElements.size()) {
*err = BAD_INDEX;
@@ -109,7 +114,8 @@
*err = BAD_TYPE;
return NULL;
}
- return elem.data.string;
+ // Need to add the '/0' at the end by specifying the length of the string.
+ return string(elem.data.string, elem.len).c_str();
}
bool LogEvent::GetBool(size_t key, status_t* err) const {
@@ -150,6 +156,29 @@
}
}
+KeyValuePair LogEvent::GetKeyValueProto(size_t key) const {
+ KeyValuePair pair;
+ pair.set_key(key);
+ // If the value is not valid, return the KeyValuePair without assigning the value.
+ // Caller can detect the error by checking the enum for "one of" proto type.
+ if (key < 1 || (key - 1) >= mElements.size()) {
+ return pair;
+ }
+ key--;
+
+ const android_log_list_element& elem = mElements[key];
+ if (elem.type == EVENT_TYPE_INT) {
+ pair.set_value_int(elem.data.int32);
+ } else if (elem.type == EVENT_TYPE_LONG) {
+ pair.set_value_int(elem.data.int64);
+ } else if (elem.type == EVENT_TYPE_STRING) {
+ pair.set_value_str(elem.data.string);
+ } else if (elem.type == EVENT_TYPE_FLOAT) {
+ pair.set_value_float(elem.data.float32);
+ }
+ return pair;
+}
+
string LogEvent::ToString() const {
ostringstream result;
result << "{ " << mTimestampNs << " (" << mTagId << ")";
@@ -166,7 +195,8 @@
} else if (elem.type == EVENT_TYPE_FLOAT) {
result << elem.data.float32;
} else if (elem.type == EVENT_TYPE_STRING) {
- result << elem.data.string;
+ // Need to add the '/0' at the end by specifying the length of the string.
+ result << string(elem.data.string, elem.len).c_str();
}
}
result << " }";
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index b523201..4102675 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -22,6 +22,7 @@
#include <log/log_event_list.h>
#include <log/log_read.h>
+#include <memory>
#include <string>
#include <vector>
@@ -40,12 +41,16 @@
/**
* Read a LogEvent from a log_msg.
*/
- explicit LogEvent(const log_msg& msg);
+ explicit LogEvent(log_msg msg);
/**
- * Read a LogEvent from an android_log_context.
+ * Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
+ * mode. Obtain this list with the getter. Make sure to call init() before attempting to read
+ * any of the values. This constructor is useful for unit-testing since we can't pass in an
+ * android_log_event_list since there is no copy constructor or assignment operator available.
*/
- explicit LogEvent(int64_t timestampNs, android_log_event_list* reader);
+ explicit LogEvent(int tag);
+
~LogEvent();
/**
@@ -80,6 +85,24 @@
*/
void ToProto(EventMetricData* out) const;
+ /*
+ * Get a KeyValuePair proto object.
+ */
+ KeyValuePair GetKeyValueProto(size_t key) const;
+
+ /**
+ * A pointer to the contained log_event_list.
+ *
+ * @return The android_log_event_list contained within.
+ */
+ android_log_event_list* GetAndroidLogEventList();
+
+ /**
+ * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
+ * and prepares the list for reading.
+ */
+ void init();
+
private:
/**
* Don't copy, it's slower. If we really need this we can add it but let's try to
@@ -98,6 +121,8 @@
void init(int64_t timestampNs, android_log_event_list* reader);
vector<android_log_list_element> mElements;
+ // Need a copy of the android_log_event_list so the strings are not cleared.
+ android_log_event_list mList;
long mTimestampNs;
int mTagId;
};
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index 71078ea..b2c88a0 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.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 "SimpleLogMatchingTracker.h"
@@ -34,10 +34,12 @@
SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
const SimpleLogEntryMatcher& matcher)
: LogMatchingTracker(name, index), mMatcher(matcher) {
- for (int i = 0; i < matcher.tag_size(); i++) {
- mTagIds.insert(matcher.tag(i));
+ if (!matcher.has_tag()) {
+ mInitialized = false;
+ } else {
+ mTagIds.insert(matcher.tag());
+ mInitialized = true;
}
- mInitialized = true;
}
SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
@@ -48,7 +50,7 @@
const unordered_map<string, int>& matcherMap,
vector<bool>& stack) {
// no need to do anything.
- return true;
+ return mInitialized;
}
void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 1c1e3c7..6aa2211 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -30,9 +30,9 @@
#include <sstream>
#include <unordered_map>
+using std::ostringstream;
using std::set;
using std::string;
-using std::ostringstream;
using std::unordered_map;
using std::vector;
@@ -91,109 +91,103 @@
bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& event) {
const int tagId = event.GetTagId();
- /*
- const unordered_map<int, long>& intMap = event.intMap;
- const unordered_map<int, string>& strMap = event.strMap;
- const unordered_map<int, float>& floatMap = event.floatMap;
- const unordered_map<int, bool>& boolMap = event.boolMap;
- */
- for (int i = 0; i < simpleMatcher.tag_size(); i++) {
- if (simpleMatcher.tag(i) != tagId) {
- continue;
- }
+ if (simpleMatcher.tag() != tagId) {
+ return false;
+ }
+ // now see if this event is interesting to us -- matches ALL the matchers
+ // defined in the metrics.
+ bool allMatched = true;
+ for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
+ auto cur = simpleMatcher.key_value_matcher(j);
- // TODO Is this right? Shouldn't this second loop be outside the outer loop?
- // If I understand correctly, the event matches if one of the tags match,
- // and ALL of the key-value matchers match. --joeo
+ // TODO: Check if this key is a magic key (eg package name).
+ // TODO: Maybe make packages a different type in the config?
+ int key = cur.key_matcher().key();
- // now see if this event is interesting to us -- matches ALL the matchers
- // defined in the metrics.
- bool allMatched = true;
- for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
- auto cur = simpleMatcher.key_value_matcher(j);
-
- // TODO: Check if this key is a magic key (eg package name).
- // TODO: Maybe make packages a different type in the config?
- int key = cur.key_matcher().key();
-
- const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
- // String fields
- status_t err = NO_ERROR;
- const char* val = event.GetString(key, &err);
- if (err == NO_ERROR && val != NULL) {
- if (!(cur.eq_string() == val)) {
- allMatched = false;
- }
+ const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
+ if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
+ // String fields
+ status_t err = NO_ERROR;
+ const char* val = event.GetString(key, &err);
+ if (err == NO_ERROR && val != NULL) {
+ if (!(cur.eq_string() == val)) {
+ allMatched = false;
}
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
- // Integer fields
- status_t err = NO_ERROR;
- int64_t val = event.GetLong(key, &err);
- if (err == NO_ERROR) {
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
- if (!(val == cur.eq_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
- if (!(val < cur.lt_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
- if (!(val > cur.gt_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
- if (!(val <= cur.lte_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
- if (!(val >= cur.gte_int())) {
- allMatched = false;
- }
- }
- }
- break;
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
- // Boolean fields
- status_t err = NO_ERROR;
- bool val = event.GetBool(key, &err);
- if (err == NO_ERROR) {
- if (!(cur.eq_bool() == val)) {
- allMatched = false;
- }
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
- // Float fields
- status_t err = NO_ERROR;
- bool val = event.GetFloat(key, &err);
- if (err == NO_ERROR) {
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
- if (!(cur.lt_float() <= val)) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
- if (!(cur.gt_float() >= val)) {
- allMatched = false;
- }
- }
- }
- } else {
- // If value matcher is not present, assume that we match.
}
- }
-
- if (allMatched) {
- return true;
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+ // Integer fields
+ status_t err = NO_ERROR;
+ int64_t val = event.GetLong(key, &err);
+ if (err == NO_ERROR) {
+ if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
+ if (!(val == cur.eq_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
+ if (!(val < cur.lt_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
+ if (!(val > cur.gt_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
+ if (!(val <= cur.lte_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+ if (!(val >= cur.gte_int())) {
+ allMatched = false;
+ }
+ }
+ }
+ break;
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
+ // Boolean fields
+ status_t err = NO_ERROR;
+ bool val = event.GetBool(key, &err);
+ if (err == NO_ERROR) {
+ if (!(cur.eq_bool() == val)) {
+ allMatched = false;
+ }
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+ // Float fields
+ status_t err = NO_ERROR;
+ bool val = event.GetFloat(key, &err);
+ if (err == NO_ERROR) {
+ if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
+ if (!(cur.lt_float() <= val)) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+ if (!(cur.gt_float() >= val)) {
+ allMatched = false;
+ }
+ }
+ }
+ } else {
+ // If value matcher is not present, assume that we match.
}
}
- return false;
+ return allMatched;
+}
+
+vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+ const std::vector<KeyMatcher>& dimensions) {
+ vector<KeyValuePair> key;
+ key.reserve(dimensions.size());
+ for (const KeyMatcher& dimension : dimensions) {
+ KeyValuePair k = event.GetKeyValueProto(dimension.key());
+ key.push_back(k);
+ }
+ return key;
}
} // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 3a5925c..4ea6f0b 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -27,6 +27,7 @@
#include <vector>
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
namespace android {
namespace os {
@@ -43,6 +44,9 @@
bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& wrapper);
+std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+ const std::vector<KeyMatcher>& dimensions);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 7df62fb..1f07914 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,35 +17,49 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#include "CountMetricProducer.h"
#include "CountAnomalyTracker.h"
+#include "CountMetricProducer.h"
+#include "stats_util.h"
#include <cutils/log.h>
#include <limits.h>
#include <stdlib.h>
+using std::map;
+using std::string;
using std::unordered_map;
+using std::vector;
namespace android {
namespace os {
namespace statsd {
-CountMetricProducer::CountMetricProducer(const CountMetric& metric, const bool hasCondition)
- : mMetric(metric),
- mStartTime(time(nullptr)),
- mCounter(0),
- mCurrentBucketStartTime(mStartTime),
+// TODO: add back AnomalyTracker.
+CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+ : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+ mMetric(metric),
// TODO: read mAnomalyTracker parameters from config file.
- mAnomalyTracker(6, 10),
- mCondition(hasCondition ? ConditionState::kUnknown : ConditionState::kTrue) {
+ mAnomalyTracker(6, 10) {
// TODO: evaluate initial conditions. and set mConditionMet.
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
- mBucketSize_sec = metric.bucket().bucket_size_millis() / 1000;
+ mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
} else {
- mBucketSize_sec = LONG_MAX;
+ mBucketSizeNs = LLONG_MAX;
}
- VLOG("created. bucket size %lu start_time: %lu", mBucketSize_sec, mStartTime);
+ // TODO: use UidMap if uid->pkg_name is required
+ mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
}
CountMetricProducer::~CountMetricProducer() {
@@ -57,54 +71,146 @@
// DropboxWriter.
}
-void CountMetricProducer::onDumpReport() {
- VLOG("dump report now...");
+static void addSlicedCounterToReport(StatsLogReport_CountMetricDataWrapper& wrapper,
+ const vector<KeyValuePair>& key,
+ const vector<CountBucketInfo>& buckets) {
+ CountMetricData* data = wrapper.add_data();
+ for (const auto& kv : key) {
+ data->add_dimension()->CopyFrom(kv);
+ }
+ for (const auto& bucket : buckets) {
+ data->add_bucket_info()->CopyFrom(bucket);
+ VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+ bucket.end_bucket_nanos(), bucket.count());
+ }
+}
+
+void CountMetricProducer::onSlicedConditionMayChange() {
+ VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+StatsLogReport CountMetricProducer::onDumpReport() {
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
+
+ StatsLogReport report;
+ report.set_metric_id(mMetric.metric_id());
+ report.set_start_report_nanos(mStartTimeNs);
+
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushCounterIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+ StatsLogReport_CountMetricDataWrapper* wrapper = report.mutable_count_metrics();
+
+ for (const auto& pair : mPastBuckets) {
+ const HashableDimensionKey& hashableKey = pair.first;
+ auto it = mDimensionKeyMap.find(hashableKey);
+ if (it == mDimensionKeyMap.end()) {
+ ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+ continue;
+ }
+
+ VLOG(" dimension key %s", hashableKey.c_str());
+ addSlicedCounterToReport(*wrapper, it->second, pair.second);
+ }
+ return report;
+ // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
}
void CountMetricProducer::onConditionChanged(const bool conditionMet) {
- VLOG("onConditionChanged");
+ VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
mCondition = conditionMet;
}
-void CountMetricProducer::onMatchedLogEvent(const LogEvent& event) {
- time_t eventTime = event.GetTimestampNs() / 1000000000;
-
+void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+ uint64_t eventTimeNs = event.GetTimestampNs();
// this is old event, maybe statsd restarted?
- if (eventTime < mStartTime) {
+ if (eventTimeNs < mStartTimeNs) {
return;
}
- if (mCondition == ConditionState::kTrue) {
- flushCounterIfNeeded(eventTime);
- mCounter++;
- mAnomalyTracker.checkAnomaly(mCounter);
- VLOG("metric %lld count %d", mMetric.metric_id(), mCounter);
+ flushCounterIfNeeded(eventTimeNs);
+
+ if (mConditionSliced) {
+ map<string, HashableDimensionKey> conditionKeys;
+ for (const auto& link : mConditionLinks) {
+ VLOG("Condition link key_in_main size %d", link.key_in_main_size());
+ HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+ conditionKeys[link.condition()] = conditionKey;
+ }
+ if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+ VLOG("metric %lld sliced condition not met", mMetric.metric_id());
+ return;
+ }
+ } else {
+ if (!mCondition) {
+ VLOG("metric %lld condition not met", mMetric.metric_id());
+ return;
+ }
}
+
+ HashableDimensionKey hashableKey;
+
+ if (mDimension.size() > 0) {
+ vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+ hashableKey = getHashableKey(key);
+ // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
+ // expects vector<KeyValuePair>.
+ if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
+ mDimensionKeyMap[hashableKey] = key;
+ }
+ } else {
+ hashableKey = DEFAULT_DIMENSION_KEY;
+ }
+
+ auto it = mCurrentSlicedCounter.find(hashableKey);
+
+ if (it == mCurrentSlicedCounter.end()) {
+ // create a counter for the new key
+ mCurrentSlicedCounter[hashableKey] = 1;
+
+ } else {
+ // increment the existing value
+ auto& count = it->second;
+ count++;
+ }
+
+ VLOG("metric %lld %s->%d", mMetric.metric_id(), hashableKey.c_str(),
+ mCurrentSlicedCounter[hashableKey]);
}
-// When a new matched event comes in, we check if it falls into the current
-// bucket. And flush the counter to the StatsLogReport and adjust the bucket if
-// needed.
-void CountMetricProducer::flushCounterIfNeeded(const time_t& eventTime) {
- if (mCurrentBucketStartTime + mBucketSize_sec > eventTime) {
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
+void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
return;
}
- // TODO: add a KeyValuePair to StatsLogReport.
- ALOGD("%lld: dump counter %d", mMetric.metric_id(), mCounter);
-
// adjust the bucket start time
- time_t numBucketsForward = (eventTime - mCurrentBucketStartTime)
- / mBucketSize_sec;
+ int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTime = mCurrentBucketStartTime +
- (numBucketsForward) * mBucketSize_sec;
+ CountBucketInfo info;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
- // reset counter
- mAnomalyTracker.addPastBucket(mCounter, numBucketsForward);
- mCounter = 0;
+ for (const auto& counter : mCurrentSlicedCounter) {
+ info.set_count(counter.second);
+ // it will auto create new vector of CountbucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[counter.first];
+ bucketList.push_back(info);
- VLOG("%lld: new bucket start time: %lu", mMetric.metric_id(), mCurrentBucketStartTime);
+ VLOG("metric %lld, dump key value: %s -> %d", mMetric.metric_id(), counter.first.c_str(),
+ counter.second);
+ }
+
+ // Reset counters
+ mCurrentSlicedCounter.clear();
+
+ mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+ VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+ (long long)mCurrentBucketStartTimeNs);
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 94abd62..f0d6025 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -19,12 +19,13 @@
#include <unordered_map>
-#include "CountAnomalyTracker.h"
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
+#include "CountAnomalyTracker.h"
#include "MetricProducer.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
using namespace std;
@@ -34,38 +35,37 @@
class CountMetricProducer : public MetricProducer {
public:
- CountMetricProducer(const CountMetric& countMetric, const bool hasCondition);
+ // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+ CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard);
virtual ~CountMetricProducer();
- void onMatchedLogEvent(const LogEvent& event) override;
+ void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
void onConditionChanged(const bool conditionMet) override;
void finish() override;
- void onDumpReport() override;
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange() override;
// TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override {};
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
private:
const CountMetric mMetric;
- const time_t mStartTime;
- // TODO: Add dimensions.
- // Counter value for the current bucket.
- int mCounter;
-
- time_t mCurrentBucketStartTime;
-
- long mBucketSize_sec;
-
CountAnomalyTracker mAnomalyTracker;
- bool mCondition;
+ // Save the past buckets and we can clear when the StatsLogReport is dumped.
+ std::unordered_map<HashableDimensionKey, std::vector<CountBucketInfo>> mPastBuckets;
- void flushCounterIfNeeded(const time_t& newEventTime);
+ // The current bucket.
+ std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+
+ void flushCounterIfNeeded(const uint64_t newEventTime);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
new file mode 100644
index 0000000..aa597f4
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -0,0 +1,384 @@
+/*
+ * 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.
+ */
+
+#define DEBUG true
+#include "DurationMetricProducer.h"
+#include "Log.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
+ const int conditionIndex, const size_t startIndex,
+ const size_t stopIndex, const size_t stopAllIndex,
+ const sp<ConditionWizard>& wizard)
+ // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+ : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
+ mMetric(metric),
+ mStartIndex(startIndex),
+ mStopIndex(stopIndex),
+ mStopAllIndex(stopAllIndex) {
+ // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
+ // them in the base class, because the proto generated CountMetric, and DurationMetric are
+ // not related. Maybe we should add a template in the future??
+ if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+ mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000000;
+ } else {
+ mBucketSizeNs = LLONG_MAX;
+ }
+
+ // TODO: use UidMap if uid->pkg_name is required
+ mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+DurationMetricProducer::~DurationMetricProducer() {
+ VLOG("~DurationMetric() called");
+}
+
+void DurationMetricProducer::finish() {
+ // TODO: write the StatsLogReport to dropbox using
+ // DropboxWriter.
+}
+
+void DurationMetricProducer::onSlicedConditionMayChange() {
+ VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+ // Now for each of the on-going event, check if the condition has changed for them.
+ for (auto& pair : mCurrentSlicedDuration) {
+ VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
+ pair.second.state);
+ if (pair.second.state == kStopped) {
+ continue;
+ }
+ bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+ ConditionState::kTrue;
+ VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+ noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+ }
+}
+
+void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+ VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+ mCondition = conditionMet;
+ // TODO: need to populate the condition change time from the event which triggers the condition
+ // change, instead of using current time.
+ for (auto& pair : mCurrentSlicedDuration) {
+ noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+ }
+}
+
+static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
+ const vector<KeyValuePair>& key,
+ const vector<DurationBucketInfo>& buckets) {
+ DurationMetricData* data = wrapper.add_data();
+ for (const auto& kv : key) {
+ data->add_dimension()->CopyFrom(kv);
+ }
+ for (const auto& bucket : buckets) {
+ data->add_bucket_info()->CopyFrom(bucket);
+ VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+ bucket.end_bucket_nanos(), bucket.duration_nanos());
+ }
+}
+
+StatsLogReport DurationMetricProducer::onDumpReport() {
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
+ StatsLogReport report;
+ report.set_metric_id(mMetric.metric_id());
+ report.set_start_report_nanos(mStartTimeNs);
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+ StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
+ for (const auto& pair : mPastBuckets) {
+ const HashableDimensionKey& hashableKey = pair.first;
+ auto it = mDimensionKeyMap.find(hashableKey);
+ if (it == mDimensionKeyMap.end()) {
+ ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+ continue;
+ }
+ VLOG(" dimension key %s", hashableKey.c_str());
+ addDurationBucketsToReport(*wrapper, it->second, pair.second);
+ }
+ return report;
+};
+
+void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+ if (event.GetTimestampNs() < mStartTimeNs) {
+ return;
+ }
+
+ flushDurationIfNeeded(event.GetTimestampNs());
+
+ if (matcherIndex == mStopAllIndex) {
+ noteStopAll(event.GetTimestampNs());
+ return;
+ }
+
+ HashableDimensionKey hashableKey;
+ if (mDimension.size() > 0) {
+ // hook up sliced counter with AnomalyMonitor.
+ vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+ hashableKey = getHashableKey(key);
+ // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects
+ // vector<KeyValuePair>.
+ if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
+ mDimensionKeyMap[hashableKey] = key;
+ }
+ } else {
+ hashableKey = DEFAULT_DIMENSION_KEY;
+ }
+
+ if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() &&
+ mConditionSliced) {
+ // add the durationInfo for the current bucket.
+ auto& durationInfo = mCurrentSlicedDuration[hashableKey];
+ auto& conditionKeys = durationInfo.conditionKeys;
+ // get and cache the keys for query condition.
+ for (const auto& link : mConditionLinks) {
+ HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+ conditionKeys[link.condition()] = conditionKey;
+ }
+ }
+
+ bool conditionMet;
+ if (mConditionSliced) {
+ const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys;
+ conditionMet =
+ mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue;
+ } else {
+ conditionMet = mCondition;
+ }
+
+ if (matcherIndex == mStartIndex) {
+ VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
+ conditionMet);
+ noteStart(hashableKey, conditionMet, event.GetTimestampNs());
+ } else if (matcherIndex == mStopIndex) {
+ VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
+ conditionMet);
+ noteStop(hashableKey, event.GetTimestampNs());
+ }
+}
+
+void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
+ const bool conditionMet,
+ const uint64_t eventTime) {
+ flushDurationIfNeeded(eventTime);
+
+ auto it = mCurrentSlicedDuration.find(key);
+ if (it == mCurrentSlicedDuration.end()) {
+ return;
+ }
+
+ switch (it->second.state) {
+ case kStarted:
+ // if condition becomes false, kStarted -> kPaused. Record the current duration.
+ if (!conditionMet) {
+ it->second.state = DurationState::kPaused;
+ it->second.lastDuration =
+ updateDuration(it->second.lastDuration,
+ eventTime - it->second.lastStartTime, mMetric.type());
+ VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
+ key.c_str());
+ }
+ break;
+ case kStopped:
+ // nothing to do if it's stopped.
+ break;
+ case kPaused:
+ // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+ // change time.
+ if (conditionMet) {
+ it->second.state = DurationState::kStarted;
+ it->second.lastStartTime = eventTime;
+ VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
+ }
+ break;
+ }
+}
+
+void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
+ const uint64_t eventTime) {
+ // this will add an empty bucket for this key if it didn't exist before.
+ DurationInfo& duration = mCurrentSlicedDuration[key];
+
+ switch (duration.state) {
+ case kStarted:
+ // It's safe to do nothing here. even if condition is not true, it means we are about
+ // to receive the condition change event.
+ break;
+ case kPaused:
+ // Safe to do nothing here. kPaused is waiting for the condition change.
+ break;
+ case kStopped:
+ if (!conditionMet) {
+ // event started, but we need to wait for the condition to become true.
+ duration.state = DurationState::kPaused;
+ break;
+ }
+ duration.state = DurationState::kStarted;
+ duration.lastStartTime = eventTime;
+ break;
+ }
+}
+
+void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+ if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
+ // we didn't see a start event before. do nothing.
+ return;
+ }
+ DurationInfo& duration = mCurrentSlicedDuration[key];
+
+ switch (duration.state) {
+ case DurationState::kStopped:
+ // already stopped, do nothing.
+ break;
+ case DurationState::kStarted: {
+ duration.state = DurationState::kStopped;
+ int64_t durationTime = eventTime - duration.lastStartTime;
+ VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
+ (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
+ duration.lastDuration =
+ updateDuration(duration.lastDuration, durationTime, mMetric.type());
+ VLOG(" record duration: %lld ", (long long)duration.lastDuration);
+ break;
+ }
+ case DurationState::kPaused: {
+ duration.state = DurationState::kStopped;
+ break;
+ }
+ }
+}
+
+int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
+ const int64_t durationTime,
+ const DurationMetric_AggregationType type) {
+ int64_t result = lastDuration;
+ switch (type) {
+ case DurationMetric_AggregationType_DURATION_SUM:
+ result += durationTime;
+ break;
+ case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+ if (lastDuration < durationTime) {
+ result = durationTime;
+ }
+ break;
+ case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
+ if (lastDuration > durationTime) {
+ result = durationTime;
+ }
+ break;
+ }
+ return result;
+}
+
+void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
+ for (auto& duration : mCurrentSlicedDuration) {
+ noteStop(duration.first, eventTime);
+ }
+}
+
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
+void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+ return;
+ }
+
+ // adjust the bucket start time
+ int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+ DurationBucketInfo info;
+ uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(endTime);
+
+ uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+ VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+ (long long)mCurrentBucketStartTimeNs);
+
+ for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
+ int64_t finalDuration = it->second.lastDuration;
+ if (it->second.state == kStarted) {
+ // the event is still on-going, duration needs to be updated.
+ int64_t durationTime = endTime - it->second.lastStartTime;
+ finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
+ }
+
+ VLOG(" final duration for last bucket: %lld", (long long)finalDuration);
+
+ // Don't record empty bucket.
+ if (finalDuration != 0) {
+ info.set_duration_nanos(finalDuration);
+ // it will auto create new vector of CountbucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[it->first];
+ bucketList.push_back(info);
+ }
+
+ // if the event is still on-going, add the buckets between previous bucket and now. Because
+ // the event has been going on across all the buckets in between.
+ // |prev_bucket|...|..|...|now_bucket|
+ if (it->second.state == kStarted) {
+ for (int i = 1; i < numBucketsForward; i++) {
+ DurationBucketInfo info;
+ info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+ info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+ info.set_duration_nanos(mBucketSizeNs);
+ auto& bucketList = mPastBuckets[it->first];
+ bucketList.push_back(info);
+ VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
+ }
+ }
+
+ if (it->second.state == DurationState::kStopped) {
+ // No need to keep buckets for events that were stopped before. If the event starts
+ // again, we will add it back.
+ mCurrentSlicedDuration.erase(it);
+ } else {
+ // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
+ // and duration.
+ it->second.lastStartTime = mCurrentBucketStartTimeNs;
+ it->second.lastDuration = 0;
+ }
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
new file mode 100644
index 0000000..44c3254
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -0,0 +1,115 @@
+/*
+ * 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 DURATION_METRIC_PRODUCER_H
+#define DURATION_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+ kStopped = 0, // The event is stopped.
+ kStarted = 1, // The event is on going.
+ kPaused = 2, // The event is started, but condition is false, clock is paused. When condition
+ // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for current on-going bucket.
+struct DurationInfo {
+ DurationState state;
+ // most recent start time.
+ int64_t lastStartTime;
+ // existing duration in current bucket. Eventually, the duration will be aggregated in
+ // the way specified in AggregateType (Sum, Max, or Min).
+ int64_t lastDuration;
+ // cache the HashableDimensionKeys we need to query the condition for this duration event.
+ std::map<string, HashableDimensionKey> conditionKeys;
+
+ DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationMetricProducer : public MetricProducer {
+public:
+ DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
+ const size_t startIndex, const size_t stopIndex,
+ const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+
+ virtual ~DurationMetricProducer();
+
+ void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
+
+ void onConditionChanged(const bool conditionMet) override;
+
+ void finish() override;
+
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange() override;
+
+ // TODO: Implement this later.
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+private:
+ const DurationMetric mMetric;
+
+ // Index of the SimpleLogEntryMatcher which defines the start.
+ const size_t mStartIndex;
+
+ // Index of the SimpleLogEntryMatcher which defines the stop.
+ const size_t mStopIndex;
+
+ // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
+ const size_t mStopAllIndex;
+
+ // Save the past buckets and we can clear when the StatsLogReport is dumped.
+ std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
+
+ // The current bucket.
+ std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+
+ void flushDurationIfNeeded(const uint64_t newEventTime);
+
+ void noteStart(const HashableDimensionKey& key, const bool conditionMet,
+ const uint64_t eventTime);
+
+ void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
+
+ void noteStopAll(const uint64_t eventTime);
+
+ static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
+ const DurationMetric_AggregationType type);
+
+ void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
+ const uint64_t eventTime);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 589df84..afaab64 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,11 +17,13 @@
#ifndef METRIC_PRODUCER_H
#define METRIC_PRODUCER_H
+#include "condition/ConditionWizard.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
#include <log/logprint.h>
#include <utils/RefBase.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
namespace android {
namespace os {
@@ -33,18 +35,57 @@
// be a no-op.
class MetricProducer : public virtual PackageInfoListener {
public:
+ MetricProducer(const int64_t startTimeNs, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ : mStartTimeNs(startTimeNs),
+ mCurrentBucketStartTimeNs(startTimeNs),
+ mCondition(conditionIndex >= 0 ? false : true),
+ mWizard(wizard),
+ mConditionTrackerIndex(conditionIndex) {
+ // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
+ mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
+ };
virtual ~MetricProducer(){};
// Consume the parsed stats log entry that already matched the "what" of the metric.
- virtual void onMatchedLogEvent(const LogEvent& event) = 0;
+ virtual void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) = 0;
virtual void onConditionChanged(const bool condition) = 0;
+ virtual void onSlicedConditionMayChange() = 0;
+
// This is called when the metric collecting is done, e.g., when there is a new configuration
// coming. MetricProducer should do the clean up, and dump existing data to dropbox.
virtual void finish() = 0;
- virtual void onDumpReport() = 0;
+ virtual StatsLogReport onDumpReport() = 0;
+
+ virtual bool isConditionSliced() const {
+ return mConditionSliced;
+ };
+
+protected:
+ const uint64_t mStartTimeNs;
+
+ uint64_t mCurrentBucketStartTimeNs;
+
+ int64_t mBucketSizeNs;
+
+ bool mCondition;
+
+ bool mConditionSliced;
+
+ sp<ConditionWizard> mWizard;
+
+ int mConditionTrackerIndex;
+
+ std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config
+
+ // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+ // that StatsLogReport wants.
+ std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
+
+ std::vector<EventConditionLink> mConditionLinks;
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 5b4ca80..c19d462 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,8 +15,6 @@
*/
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#define VLOG(...) \
- if (DEBUG) ALOGD(__VA_ARGS__);
#include "MetricsManager.h"
#include <log/logprint.h>
@@ -58,6 +56,17 @@
}
}
+vector<StatsLogReport> MetricsManager::onDumpReport() {
+ VLOG("=========================Metric Reports Start==========================");
+ // one StatsLogReport per MetricProduer
+ vector<StatsLogReport> reportList;
+ for (auto& metric : mAllMetricProducers) {
+ reportList.push_back(metric->onDumpReport());
+ }
+ VLOG("=========================Metric Reports End==========================");
+ return reportList;
+}
+
// Consume the stats log if it's interesting to this metric.
void MetricsManager::onLogEvent(const LogEvent& event) {
if (!mConfigValid) {
@@ -71,6 +80,7 @@
}
// Since at least one of the metrics is interested in this event, we parse it now.
+ ALOGD("%s", event.ToString().c_str());
vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
for (auto& matcher : mAllLogEntryMatchers) {
@@ -93,20 +103,34 @@
ConditionState::kNotEvaluated);
// A bitmap to track if a condition has changed value.
vector<bool> changedCache(mAllConditionTrackers.size(), false);
+ vector<bool> slicedChangedCache(mAllConditionTrackers.size(), false);
for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
if (conditionToBeEvaluated[i] == false) {
continue;
}
-
sp<ConditionTracker>& condition = mAllConditionTrackers[i];
condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
- changedCache);
- if (changedCache[i]) {
- auto pair = mConditionToMetricMap.find(i);
- if (pair != mConditionToMetricMap.end()) {
- auto& metricList = pair->second;
- for (auto metricIndex : metricList) {
+ changedCache, slicedChangedCache);
+ }
+
+ for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
+ if (changedCache[i] == false && slicedChangedCache[i] == false) {
+ continue;
+ }
+ auto pair = mConditionToMetricMap.find(i);
+ if (pair != mConditionToMetricMap.end()) {
+ auto& metricList = pair->second;
+ for (auto metricIndex : metricList) {
+ // metric cares about non sliced condition, and it's changed.
+ // Push the new condition to it directly.
+ if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+ // metric cares about sliced conditions, and it may have changed. Send
+ // notification, and the metric can query the sliced conditions that are
+ // interesting to it.
+ } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
+ slicedChangedCache[i]) {
+ mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
}
}
}
@@ -119,7 +143,7 @@
if (pair != mTrackerToMetricMap.end()) {
auto& metricList = pair->second;
for (const int metricIndex : metricList) {
- mAllMetricProducers[metricIndex]->onMatchedLogEvent(event);
+ mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
}
}
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 7aca0b5..56f57d3 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -43,17 +43,19 @@
// Called when everything should wrap up. We are about to finish (e.g., new config comes).
void finish();
+ // Config source owner can call onDumpReport() to get all the metrics collected.
+ std::vector<StatsLogReport> onDumpReport();
+
private:
// All event tags that are interesting to my metrics.
std::set<int> mTagIds;
// We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
// MetricManager. There are relationship between them, and the relationship are denoted by index
- // instead of poiters. The reasons for this are: (1) the relationship between them are
+ // instead of pointers. The reasons for this are: (1) the relationship between them are
// complicated, store index instead of pointers reduce the risk of A holds B's sp, and B holds
// A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get the
// related results from a cache using the index.
- // TODO: using unique_ptr may be more appriopreate?
// Hold all the log entry matchers from the config.
std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 6fdd228..23071aa 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -19,6 +19,7 @@
#include "../matchers/CombinationLogMatchingTracker.h"
#include "../matchers/SimpleLogMatchingTracker.h"
#include "CountMetricProducer.h"
+#include "DurationMetricProducer.h"
#include "stats_util.h"
using std::set;
@@ -30,11 +31,23 @@
namespace os {
namespace statsd {
+int getTrackerIndex(const string& name, const unordered_map<string, int>& logTrackerMap) {
+ auto logTrackerIt = logTrackerMap.find(name);
+ if (logTrackerIt == logTrackerMap.end()) {
+ ALOGW("cannot find the LogEventMatcher %s in config", name.c_str());
+ return MATCHER_NOT_FOUND;
+ }
+ return logTrackerIt->second;
+}
+
bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
vector<sp<LogMatchingTracker>>& allLogEntryMatchers, set<int>& allTagIds) {
vector<LogEntryMatcher> matcherConfigs;
+ const int logEntryMatcherCount = config.log_entry_matcher_size();
+ matcherConfigs.reserve(logEntryMatcherCount);
+ allLogEntryMatchers.reserve(logEntryMatcherCount);
- for (int i = 0; i < config.log_entry_matcher_size(); i++) {
+ for (int i = 0; i < logEntryMatcherCount; i++) {
const LogEntryMatcher& logMatcher = config.log_entry_matcher(i);
int index = allLogEntryMatchers.size();
@@ -77,8 +90,11 @@
vector<sp<ConditionTracker>>& allConditionTrackers,
unordered_map<int, std::vector<int>>& trackerToConditionMap) {
vector<Condition> conditionConfigs;
+ const int conditionTrackerCount = config.condition_size();
+ conditionConfigs.reserve(conditionTrackerCount);
+ allConditionTrackers.reserve(conditionTrackerCount);
- for (int i = 0; i < config.condition_size(); i++) {
+ for (int i = 0; i < conditionTrackerCount; i++) {
const Condition& condition = config.condition(i);
int index = allConditionTrackers.size();
switch (condition.contents_case()) {
@@ -121,9 +137,14 @@
bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
const unordered_map<string, int>& conditionTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap) {
+ sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
+ const int allMetricsCount = config.count_metric_size() + config.duration_metric_size();
+ allMetricProducers.reserve(allMetricsCount);
+
// Build MetricProducers for each metric defined in config.
// (1) build CountMetricProducer
for (int i = 0; i < config.count_metric_size(); i++) {
@@ -133,36 +154,109 @@
return false;
}
+ int metricIndex = allMetricProducers.size();
+
auto logTrackerIt = logTrackerMap.find(metric.what());
if (logTrackerIt == logTrackerMap.end()) {
ALOGW("cannot find the LogEntryMatcher %s in config", metric.what().c_str());
return false;
}
+ int logTrackerIndex = logTrackerIt->second;
+ auto& metric_list = trackerToMetricMap[logTrackerIndex];
+ metric_list.push_back(metricIndex);
sp<MetricProducer> countProducer;
- int metricIndex = allMetricProducers.size();
+
if (metric.has_condition()) {
auto condition_it = conditionTrackerMap.find(metric.condition());
if (condition_it == conditionTrackerMap.end()) {
ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
return false;
}
- countProducer = new CountMetricProducer(metric, true /*has condition*/);
+
+ for (const auto& link : metric.links()) {
+ auto it = conditionTrackerMap.find(link.condition());
+ if (it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ return false;
+ }
+ allConditionTrackers[condition_it->second]->setSliced(true);
+ allConditionTrackers[it->second]->setSliced(true);
+ allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
+ link.key_in_condition().begin(), link.key_in_condition().end()));
+ }
+
+ countProducer = new CountMetricProducer(metric, condition_it->second, wizard);
// will create new vector if not exist before.
auto& metricList = conditionToMetricMap[condition_it->second];
metricList.push_back(metricIndex);
} else {
- countProducer = new CountMetricProducer(metric, false /*no condition*/);
+ countProducer = new CountMetricProducer(metric, -1 /*no condition*/, wizard);
}
-
- int logTrackerIndex = logTrackerIt->second;
- auto& metric_list = trackerToMetricMap[logTrackerIndex];
- metric_list.push_back(metricIndex);
allMetricProducers.push_back(countProducer);
}
- // TODO: build other types of metrics too.
+ for (int i = 0; i < config.duration_metric_size(); i++) {
+ int metricIndex = allMetricProducers.size();
+ const DurationMetric metric = config.duration_metric(i);
+ if (!metric.has_start()) {
+ ALOGW("cannot find start in DurationMetric %lld", metric.metric_id());
+ return false;
+ }
+ int trackerIndices[] = {-1, -1, -1};
+ trackerIndices[0] = getTrackerIndex(metric.start(), logTrackerMap);
+
+ if (metric.has_stop()) {
+ trackerIndices[1] = getTrackerIndex(metric.stop(), logTrackerMap);
+ }
+
+ if (metric.has_stop_all()) {
+ trackerIndices[2] = getTrackerIndex(metric.stop_all(), logTrackerMap);
+ }
+
+ for (const int& index : trackerIndices) {
+ if (index == MATCHER_NOT_FOUND) {
+ return false;
+ }
+ if (index >= 0) {
+ auto& metric_list = trackerToMetricMap[index];
+ metric_list.push_back(metricIndex);
+ }
+ }
+
+ int conditionIndex = -1;
+
+ if (metric.has_predicate()) {
+ auto condition_it = conditionTrackerMap.find(metric.predicate());
+ if (condition_it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", metric.predicate().c_str());
+ return false;
+ }
+ conditionIndex = condition_it->second;
+
+ for (const auto& link : metric.links()) {
+ auto it = conditionTrackerMap.find(link.condition());
+ if (it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ return false;
+ }
+ allConditionTrackers[condition_it->second]->setSliced(true);
+ allConditionTrackers[it->second]->setSliced(true);
+ allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
+ link.key_in_condition().begin(), link.key_in_condition().end()));
+ }
+
+ auto& metricList = conditionToMetricMap[conditionIndex];
+ metricList.push_back(metricIndex);
+ }
+
+ sp<MetricProducer> durationMetric =
+ new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
+ trackerIndices[1], trackerIndices[2], wizard);
+
+ allMetricProducers.push_back(durationMetric);
+ }
return true;
}
@@ -187,8 +281,8 @@
return false;
}
- if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allMetricProducers,
- conditionToMetricMap, trackerToMetricMap)) {
+ if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
+ allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
ALOGE("initMetricProducers failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 5f1f295..38149a6 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -59,7 +59,8 @@
const std::unordered_map<std::string, int>& logTrackerMap,
std::unordered_map<std::string, int>& conditionTrackerMap,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
- std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+ std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+ std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks);
// Initialize MetricProducers.
// input:
@@ -71,12 +72,14 @@
// [conditionToMetricMap]: contains the mapping from condition tracker index to
// the list of MetricProducer index
// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
-bool initMetrics(const StatsdConfig& config,
- const std::unordered_map<std::string, int>& logTrackerMap,
- const std::unordered_map<std::string, int>& conditionTrackerMap,
- std::vector<sp<MetricProducer>>& allMetricProducers,
- std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
- std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
+bool initMetrics(
+ const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
+ const std::unordered_map<std::string, int>& conditionTrackerMap,
+ const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ std::vector<sp<MetricProducer>>& allMetricProducers,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
// Initialize MetricManager from StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
@@ -88,6 +91,8 @@
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+int getTrackerIndex(const std::string& name, const std::unordered_map<string, int>& logTrackerMap);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4ca06fa..29cd94b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -106,7 +106,7 @@
repeated CountMetricData data = 1;
}
message DurationMetricDataWrapper {
- repeated CountMetricData data = 1;
+ repeated DurationMetricData data = 1;
}
oneof data {
EventMetricDataWrapper event_metrics = 4;
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
index 5157adf..fcce2ff 100644
--- a/cmds/statsd/src/stats_util.cpp
+++ b/cmds/statsd/src/stats_util.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include <log/log_event_list.h>
#include "stats_util.h"
+#include <log/log_event_list.h>
namespace android {
namespace os {
@@ -128,6 +128,35 @@
return eventMetricData;
}
+// There is no existing hash function for the dimension key ("repeated KeyValuePair").
+// Temporarily use a string concatenation as the hashable key.
+// TODO: Find a better hash function for std::vector<KeyValuePair>.
+HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
+ std::string flattened;
+ for (const KeyValuePair& pair : keys) {
+ flattened += std::to_string(pair.key());
+ flattened += ":";
+ switch (pair.value_case()) {
+ case KeyValuePair::ValueCase::kValueStr:
+ flattened += pair.value_str();
+ break;
+ case KeyValuePair::ValueCase::kValueInt:
+ flattened += std::to_string(pair.value_int());
+ break;
+ case KeyValuePair::ValueCase::kValueBool:
+ flattened += std::to_string(pair.value_bool());
+ break;
+ case KeyValuePair::ValueCase::kValueFloat:
+ flattened += std::to_string(pair.value_float());
+ break;
+ default:
+ break;
+ }
+ flattened += "|";
+ }
+ return flattened;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 38174bf..575588b 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef PARSE_UTIL_H
-#define PARSE_UTIL_H
+#ifndef STATS_UTIL_H
+#define STATS_UTIL_H
#include "logd/LogReader.h"
#include "storage/DropboxWriter.h"
@@ -26,12 +26,19 @@
namespace os {
namespace statsd {
+#define DEFAULT_DIMENSION_KEY ""
+#define MATCHER_NOT_FOUND -2
+#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
+
+typedef std::string HashableDimensionKey;
+
EventMetricData parse(log_msg msg);
int getTagId(log_msg msg);
+std::string getHashableKey(std::vector<KeyValuePair> key);
} // namespace statsd
} // namespace os
} // namespace android
-#endif // PARSE_UTIL_H
+#endif // STATS_UTIL_H
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index d7702cd..afb3f2b 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -55,7 +55,7 @@
}
message SimpleLogEntryMatcher {
- repeated int32 tag = 1;
+ optional int32 tag = 1;
repeated KeyValueMatcher key_value_matcher = 2;
}
@@ -103,12 +103,28 @@
optional int64 bucket_size_millis = 1;
}
+message Alert {
+ message IncidentdDetails {
+ optional string alert_name = 1;
+ repeated int32 incidentd_sections = 2;
+ }
+ optional IncidentdDetails incidentd_details = 1;
+
+ optional int32 number_of_buckets = 3;
+
+ optional int32 refractory_period_secs = 4;
+
+ optional int64 trigger_if_gt = 5;
+}
+
message EventMetric {
optional int64 metric_id = 1;
optional string what = 2;
optional string condition = 3;
+
+ repeated EventConditionLink links = 4;
}
message CountMetric {
@@ -121,26 +137,73 @@
repeated KeyMatcher dimension = 4;
optional Bucket bucket = 5;
+
+ repeated Alert alerts = 6;
+
+ optional bool include_in_output = 7;
+
+ repeated EventConditionLink links = 8;
}
message DurationMetric {
optional int64 metric_id = 1;
+ optional string start = 2;
+
+ optional string stop = 3;
+
+ optional string stop_all = 4;
+
enum AggregationType {
DURATION_SUM = 1;
DURATION_MAX_SPARSE = 2;
DURATION_MIN_SPARSE = 3;
}
- optional AggregationType type = 2;
+ optional AggregationType type = 5;
- optional string predicate = 3;
+ optional string predicate = 6;
- repeated KeyMatcher dimension = 4;
+ repeated KeyMatcher dimension = 7;
- optional Bucket bucket = 5;
+ optional Bucket bucket = 8;
+
+ repeated EventConditionLink links = 9;
}
+message ValueMetric {
+ optional int64 metric_id = 1;
+
+ optional string what = 2;
+
+ optional int32 value_field = 3;
+
+ optional string condition = 4;
+
+ repeated KeyMatcher dimension = 5;
+
+ optional Bucket bucket = 6;
+
+ enum Operation {
+ SUM_DIFF = 1;
+ MIN_DIFF = 2;
+ MAX_DIFF = 3;
+ SUM = 4;
+ MIN = 5;
+ MAX = 6;
+ FIRST = 7;
+ LAST = 8;
+ }
+ optional Operation operation = 7;
+}
+
+message EventConditionLink {
+ optional string condition = 1;
+
+ repeated KeyMatcher key_in_main = 2;
+ repeated KeyMatcher key_in_condition = 3;
+};
+
message StatsdConfig {
optional int64 config_id = 1;
@@ -148,7 +211,11 @@
repeated CountMetric count_metric = 3;
- repeated LogEntryMatcher log_entry_matcher = 4;
+ repeated ValueMetric value_metric = 4;
- repeated Condition condition = 5;
+ repeated DurationMetric duration_metric = 5;
+
+ repeated LogEntryMatcher log_entry_matcher = 6;
+
+ repeated Condition condition = 7;
}
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 9d8ba92..fdfe8ef 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -40,14 +40,12 @@
// Set up the matcher
LogEntryMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
- simpleMatcher->add_tag(TAG_ID);
+ simpleMatcher->set_tag(TAG_ID);
- // Set up the event
- android_log_event_list list(TAG_ID);
+ LogEvent event(TAG_ID);
// Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
@@ -57,20 +55,20 @@
// Set up the matcher
LogEntryMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
- simpleMatcher->add_tag(TAG_ID);
+ simpleMatcher->set_tag(TAG_ID);
auto keyValue1 = simpleMatcher->add_key_value_matcher();
keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1);
auto keyValue2 = simpleMatcher->add_key_value_matcher();
keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
// Set up the event
- android_log_event_list list(TAG_ID);
- list << true;
- list << false;
+ LogEvent event(TAG_ID);
+ auto list = event.GetAndroidLogEventList();
+ *list << true;
+ *list << false;
// Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
keyValue1->set_eq_bool(true);
@@ -94,18 +92,18 @@
// Set up the matcher
LogEntryMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
- simpleMatcher->add_tag(TAG_ID);
+ simpleMatcher->set_tag(TAG_ID);
auto keyValue = simpleMatcher->add_key_value_matcher();
keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
keyValue->set_eq_string("some value");
// Set up the event
- android_log_event_list list(TAG_ID);
- list << "some value";
+ LogEvent event(TAG_ID);
+ auto list = event.GetAndroidLogEventList();
+ *list << "some value";
// Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
@@ -115,17 +113,17 @@
// Set up the matcher
LogEntryMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
- simpleMatcher->add_tag(TAG_ID);
+
+ simpleMatcher->set_tag(TAG_ID);
auto keyValue = simpleMatcher->add_key_value_matcher();
keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
// Set up the event
- android_log_event_list list(TAG_ID);
- list << 11;
+ LogEvent event(TAG_ID);
+ auto list = event.GetAndroidLogEventList();
+ *list << 11;
- // Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
@@ -176,7 +174,8 @@
// Set up the matcher
LogEntryMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
- simpleMatcher->add_tag(TAG_ID);
+ simpleMatcher->set_tag(TAG_ID);
+
auto keyValue = simpleMatcher->add_key_value_matcher();
keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
@@ -199,7 +198,7 @@
// Helper for the composite matchers.
void addSimpleMatcher(SimpleLogEntryMatcher* simpleMatcher, int tag, int key, int val) {
- simpleMatcher->add_tag(tag);
+ simpleMatcher->set_tag(tag);
auto keyValue = simpleMatcher->add_key_value_matcher();
keyValue->mutable_key_matcher()->set_key(key);
keyValue->set_eq_int(val);
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 32661dc..b000e13 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -45,7 +45,7 @@
eventMatcher->set_name("SCREEN_IS_ON");
SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -55,7 +55,7 @@
eventMatcher->set_name("SCREEN_IS_OFF");
simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -80,7 +80,7 @@
eventMatcher->set_name("SCREEN_IS_ON");
SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -106,7 +106,7 @@
eventMatcher->set_name("SCREEN_IS_ON");
SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -132,7 +132,7 @@
eventMatcher->set_name("SCREEN_IS_ON");
SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
@@ -142,7 +142,7 @@
eventMatcher->set_name("SCREEN_IS_OFF");
simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 969b19e..37bb6b0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -20,17 +20,19 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
-import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.IServiceConnection;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -1051,43 +1053,23 @@
* The appWidgetId specified must already be bound to the calling AppWidgetHost via
* {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
*
- * @param packageName The package from which the binding is requested.
* @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
* @param intent The intent of the service which will be providing the data to the
* RemoteViewsAdapter.
* @param connection The callback interface to be notified when a connection is made or lost.
+ * @param flags Flags used for binding to the service
+ *
+ * @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
* @hide
*/
- public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
- IBinder connection) {
+ public boolean bindRemoteViewsService(Context context, int appWidgetId, Intent intent,
+ IServiceConnection connection, @Context.BindServiceFlags int flags) {
if (mService == null) {
- return;
+ return false;
}
try {
- mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Unbinds the RemoteViewsService for a given appWidgetId and intent.
- *
- * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
- * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
- *
- * @param packageName The package from which the binding is requested.
- * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
- * @param intent The intent of the service which will be providing the data to the
- * RemoteViewsAdapter.
- * @hide
- */
- public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
- if (mService == null) {
- return;
- }
- try {
- mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
+ return mService.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent,
+ context.getIApplicationThread(), context.getActivityToken(), connection, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 20fbf04..c165fb3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3604,7 +3604,6 @@
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.text.ClipboardManager} for accessing and modifying
* {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
*
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 79310e2..16b1452 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -31,7 +31,6 @@
* RFC 4301.
*/
public final class IpSecAlgorithm implements Parcelable {
-
/**
* AES-CBC Encryption/Ciphering Algorithm.
*
@@ -68,6 +67,7 @@
* <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
*/
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
/**
* SHA512 HMAC Authentication/Integrity Algorithm
*
@@ -75,8 +75,24 @@
*/
public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+ /**
+ * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for this key are {128, 192, 256}.
+ *
+ * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+ */
+ public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
/** @hide */
- @StringDef({CRYPT_AES_CBC, AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, AUTH_HMAC_SHA512})
+ @StringDef({
+ CRYPT_AES_CBC,
+ AUTH_HMAC_MD5,
+ AUTH_HMAC_SHA1,
+ AUTH_HMAC_SHA256,
+ AUTH_HMAC_SHA512,
+ AUTH_CRYPT_AES_GCM
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface AlgorithmName {}
@@ -102,7 +118,7 @@
* @param algoName precise name of the algorithm to be used.
* @param key non-null Key padded to a multiple of 8 bits.
* @param truncLenBits the number of bits of output hash to use; only meaningful for
- * Authentication.
+ * Authentication or Authenticated Encryption (equivalent to ICV length).
*/
public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
if (!isTruncationLengthValid(algoName, truncLenBits)) {
@@ -175,6 +191,8 @@
return (truncLenBits >= 192 && truncLenBits <= 384);
case AUTH_HMAC_SHA512:
return (truncLenBits >= 256 && truncLenBits <= 512);
+ case AUTH_CRYPT_AES_GCM:
+ return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128);
default:
return false;
}
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 632b7fc..61b13a9 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -50,6 +50,9 @@
// Authentication Algorithm
private IpSecAlgorithm mAuthentication;
+ // Authenticated Encryption Algorithm
+ private IpSecAlgorithm mAuthenticatedEncryption;
+
@Override
public String toString() {
return new StringBuilder()
@@ -59,6 +62,8 @@
.append(mEncryption)
.append(", mAuthentication=")
.append(mAuthentication)
+ .append(", mAuthenticatedEncryption=")
+ .append(mAuthenticatedEncryption)
.append("}")
.toString();
}
@@ -118,6 +123,11 @@
mFlow[direction].mAuthentication = authentication;
}
+ /** Set the authenticated encryption algorithm for a given direction */
+ public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
+ mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+ }
+
public void setNetwork(Network network) {
mNetwork = network;
}
@@ -163,6 +173,10 @@
return mFlow[direction].mAuthentication;
}
+ public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
+ return mFlow[direction].mAuthenticatedEncryption;
+ }
+
public Network getNetwork() {
return mNetwork;
}
@@ -199,9 +213,11 @@
out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags);
out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags);
out.writeInt(mEncapType);
out.writeInt(mEncapSocketResourceId);
out.writeInt(mEncapRemotePort);
@@ -221,11 +237,15 @@
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
mEncapType = in.readInt();
mEncapSocketResourceId = in.readInt();
mEncapRemotePort = in.readInt();
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index e15a2c6..48b5bd5 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -281,6 +281,8 @@
* <p>If encryption is set for a given direction without also providing an SPI for that
* direction, creation of an IpSecTransform will fail upon calling a build() method.
*
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
* @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
*/
@@ -296,6 +298,8 @@
* <p>If authentication is set for a given direction without also providing an SPI for that
* direction, creation of an IpSecTransform will fail upon calling a build() method.
*
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
* @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
*/
@@ -306,6 +310,29 @@
}
/**
+ * Add an authenticated encryption algorithm to the transform for the given direction.
+ *
+ * <p>If an authenticated encryption algorithm is set for a given direction without also
+ * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
+ * a build() method.
+ *
+ * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
+ * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
+ * referred to in RFC 4301)
+ *
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
+ * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+ * be applied.
+ */
+ public IpSecTransform.Builder setAuthenticatedEncryption(
+ @TransformDirection int direction, IpSecAlgorithm algo) {
+ mConfig.setAuthenticatedEncryption(direction, algo);
+ return this;
+ }
+
+ /**
* Set the SPI, which uniquely identifies a particular IPsec session from others. Because
* IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
* given destination address.
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index 84119bd..b7e7b17 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -43,6 +43,13 @@
return mValueLong;
}
+ /**
+ * @hide
+ */
+ public void setLong(long val) {
+ mValueLong = val;
+ }
+
/*
* Parcel read/write code must be kept in sync with
* frameworks/native/services/batteryservice/BatteryProperty.cpp
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 5995696..8682c01 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -447,8 +447,7 @@
/**
* Returns the max duration if it is being tracked.
- * Not all Timer subclasses track the max, total, current durations.
-
+ * Not all Timer subclasses track the max, total, and current durations.
*/
public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
return -1;
@@ -456,14 +455,14 @@
/**
* Returns the current time the timer has been active, if it is being tracked.
- * Not all Timer subclasses track the max, total, current durations.
+ * Not all Timer subclasses track the max, total, and current durations.
*/
public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
return -1;
}
/**
- * Returns the current time the timer has been active, if it is being tracked.
+ * Returns the total time the timer has been active, if it is being tracked.
*
* Returns the total cumulative duration (i.e. sum of past durations) that this timer has
* been on since reset.
@@ -471,7 +470,7 @@
* depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
* time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
* the actual total time.
- * Not all Timer subclasses track the max, total, current durations.
+ * Not all Timer subclasses track the max, total, and current durations.
*/
public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
return -1;
@@ -600,9 +599,17 @@
public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
public abstract int getWifiScanCount(int which);
+ /**
+ * Returns the timer keeping track of wifi scans.
+ */
+ public abstract Timer getWifiScanTimer();
public abstract int getWifiScanBackgroundCount(int which);
public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
+ /**
+ * Returns the timer keeping track of background wifi scans.
+ */
+ public abstract Timer getWifiScanBackgroundTimer();
public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
public abstract int getWifiBatchedScanCount(int csphBin, int which);
public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -2824,6 +2831,10 @@
}
}
+ private static long roundUsToMs(long timeUs) {
+ return (timeUs + 500) / 1000;
+ }
+
private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
@@ -3028,8 +3039,7 @@
Timer timer, long rawRealtime, int which) {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
- / 1000;
+ final long totalTime = roundUsToMs(timer.getTotalTimeLocked(rawRealtime, which));
final int count = timer.getCountLocked(which);
if (totalTime != 0 || count != 0) {
dumpLine(pw, uid, category, type, totalTime, count);
@@ -3052,12 +3062,26 @@
return;
}
// Convert from microseconds to milliseconds with rounding
- final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtimeUs, which) + 500) / 1000;
+ final long timeMs = roundUsToMs(timer.getTotalTimeLocked(rawRealtimeUs, which));
final int count = timer.getCountLocked(which);
- if (totalTimeMs != 0 || count != 0) {
+ final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs / 1000);
+ final long curDurationMs = timer.getCurrentDurationMsLocked(rawRealtimeUs / 1000);
+ final long totalDurationMs = timer.getTotalDurationMsLocked(rawRealtimeUs / 1000);
+ if (timeMs != 0 || count != 0 || maxDurationMs != -1 || curDurationMs != -1
+ || totalDurationMs != -1) {
final long token = proto.start(fieldId);
- proto.write(TimerProto.DURATION_MS, totalTimeMs);
+ proto.write(TimerProto.DURATION_MS, timeMs);
proto.write(TimerProto.COUNT, count);
+ // These values will be -1 for timers that don't implement the functionality.
+ if (maxDurationMs != -1) {
+ proto.write(TimerProto.MAX_DURATION_MS, maxDurationMs);
+ }
+ if (curDurationMs != -1) {
+ proto.write(TimerProto.CURRENT_DURATION_MS, curDurationMs);
+ }
+ if (totalDurationMs != -1) {
+ proto.write(TimerProto.TOTAL_DURATION_MS, totalDurationMs);
+ }
proto.end(token);
}
}
@@ -3770,7 +3794,7 @@
linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
rawRealtime, "w", which, linePrefix);
- // Only log if we had at lease one wakelock...
+ // Only log if we had at least one wakelock...
if (sb.length() > 0) {
String name = wakelocks.keyAt(iw);
if (name.indexOf(',') >= 0) {
@@ -6126,9 +6150,8 @@
return;
}
int count = steps.mNumStepDurations;
- long token;
for (int i = 0; i < count; ++i) {
- token = proto.start(fieldId);
+ long token = proto.start(fieldId);
proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
@@ -6621,15 +6644,443 @@
}
if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
- // TODO: implement dumpProtoAppsLocked(proto, apps);
- dumpProtoSystemLocked(context, proto, (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+ final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
+ (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+ helper.create(this);
+ helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+
+ dumpProtoAppsLocked(proto, helper, apps);
+ dumpProtoSystemLocked(proto, helper);
}
proto.end(bToken);
proto.flush();
}
- private void dumpProtoSystemLocked(Context context, ProtoOutputStream proto, boolean wifiOnly) {
+ private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
+ List<ApplicationInfo> apps) {
+ final int which = STATS_SINCE_CHARGED;
+ final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtimeMs = SystemClock.elapsedRealtime();
+ final long rawRealtimeUs = rawRealtimeMs * 1000;
+ final long batteryUptimeUs = getBatteryUptime(rawUptimeUs);
+
+ SparseArray<ArrayList<String>> aidToPackages = new SparseArray<>();
+ if (apps != null) {
+ for (int i = 0; i < apps.size(); ++i) {
+ ApplicationInfo ai = apps.get(i);
+ int aid = UserHandle.getAppId(ai.uid);
+ ArrayList<String> pkgs = aidToPackages.get(aid);
+ if (pkgs == null) {
+ pkgs = new ArrayList<String>();
+ aidToPackages.put(aid, pkgs);
+ }
+ pkgs.add(ai.packageName);
+ }
+ }
+
+ SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
+ final List<BatterySipper> sippers = helper.getUsageList();
+ if (sippers != null) {
+ for (int i = 0; i < sippers.size(); ++i) {
+ final BatterySipper bs = sippers.get(i);
+ if (bs.drainType != BatterySipper.DrainType.APP) {
+ // Others are handled by dumpProtoSystemLocked()
+ continue;
+ }
+ uidToSipper.put(bs.uidObj.getUid(), bs);
+ }
+ }
+
+ SparseArray<? extends Uid> uidStats = getUidStats();
+ final int n = uidStats.size();
+ for (int iu = 0; iu < n; ++iu) {
+ final long uTkn = proto.start(BatteryStatsProto.UIDS);
+ final Uid u = uidStats.valueAt(iu);
+
+ final int uid = uidStats.keyAt(iu);
+ proto.write(UidProto.UID, uid);
+
+ // Print packages and apk stats (UID_DATA & APK_DATA)
+ ArrayList<String> pkgs = aidToPackages.get(UserHandle.getAppId(uid));
+ if (pkgs == null) {
+ pkgs = new ArrayList<String>();
+ }
+ final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats =
+ u.getPackageStats();
+ for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+ String pkg = packageStats.keyAt(ipkg);
+ final ArrayMap<String, ? extends Uid.Pkg.Serv> serviceStats =
+ packageStats.valueAt(ipkg).getServiceStats();
+ if (serviceStats.size() == 0) {
+ // Due to the way ActivityManagerService logs wakeup alarms, some packages (for
+ // example, "android") may be included in the packageStats that aren't part of
+ // the UID. If they don't have any services, then they shouldn't be listed here.
+ // These packages won't be a part in the pkgs List.
+ continue;
+ }
+
+ final long pToken = proto.start(UidProto.PACKAGES);
+ proto.write(UidProto.Package.NAME, pkg);
+ // Remove from the packages list since we're logging it here.
+ pkgs.remove(pkg);
+
+ for (int isvc = serviceStats.size() - 1; isvc >= 0; --isvc) {
+ final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+ long sToken = proto.start(UidProto.Package.SERVICES);
+
+ proto.write(UidProto.Package.Service.NAME, serviceStats.keyAt(isvc));
+ proto.write(UidProto.Package.Service.START_DURATION_MS,
+ roundUsToMs(ss.getStartTime(batteryUptimeUs, which)));
+ proto.write(UidProto.Package.Service.START_COUNT, ss.getStarts(which));
+ proto.write(UidProto.Package.Service.LAUNCH_COUNT, ss.getLaunches(which));
+
+ proto.end(sToken);
+ }
+ proto.end(pToken);
+ }
+ // Print any remaining packages that weren't in the packageStats map. pkgs is pulled
+ // from PackageManager data. Packages are only included in packageStats if there was
+ // specific data tracked for them (services and wakeup alarms, etc.).
+ for (String p : pkgs) {
+ final long pToken = proto.start(UidProto.PACKAGES);
+ proto.write(UidProto.Package.NAME, p);
+ proto.end(pToken);
+ }
+
+ // Total wakelock data (AGGREGATED_WAKELOCK_DATA)
+ if (u.getAggregatedPartialWakelockTimer() != null) {
+ final Timer timer = u.getAggregatedPartialWakelockTimer();
+ // Times are since reset (regardless of 'which')
+ final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+ final Timer bgTimer = timer.getSubTimer();
+ final long bgTimeMs = bgTimer != null
+ ? bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ final long awToken = proto.start(UidProto.AGGREGATED_WAKELOCK);
+ proto.write(UidProto.AggregatedWakelock.PARTIAL_DURATION_MS, totTimeMs);
+ proto.write(UidProto.AggregatedWakelock.BACKGROUND_PARTIAL_DURATION_MS, bgTimeMs);
+ proto.end(awToken);
+ }
+
+ // Audio (AUDIO_DATA)
+ dumpTimer(proto, UidProto.AUDIO, u.getAudioTurnedOnTimer(), rawRealtimeUs, which);
+
+ // Bluetooth Controller (BLUETOOTH_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, UidProto.BLUETOOTH_CONTROLLER,
+ u.getBluetoothControllerActivity(), which);
+
+ // BLE scans (BLUETOOTH_MISC_DATA) (uses totalDurationMsLocked and MaxDurationMsLocked)
+ final Timer bleTimer = u.getBluetoothScanTimer();
+ if (bleTimer != null) {
+ final long bmToken = proto.start(UidProto.BLUETOOTH_MISC);
+
+ dumpTimer(proto, UidProto.BluetoothMisc.APPORTIONED_BLE_SCAN, bleTimer,
+ rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN,
+ u.getBluetoothScanBackgroundTimer(), rawRealtimeUs, which);
+ // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+ dumpTimer(proto, UidProto.BluetoothMisc.UNOPTIMIZED_BLE_SCAN,
+ u.getBluetoothUnoptimizedScanTimer(), rawRealtimeUs, which);
+ // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+ dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_UNOPTIMIZED_BLE_SCAN,
+ u.getBluetoothUnoptimizedScanBackgroundTimer(), rawRealtimeUs, which);
+ // Result counters
+ proto.write(UidProto.BluetoothMisc.BLE_SCAN_RESULT_COUNT,
+ u.getBluetoothScanResultCounter() != null
+ ? u.getBluetoothScanResultCounter().getCountLocked(which) : 0);
+ proto.write(UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN_RESULT_COUNT,
+ u.getBluetoothScanResultBgCounter() != null
+ ? u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0);
+
+ proto.end(bmToken);
+ }
+
+ // Camera (CAMERA_DATA)
+ dumpTimer(proto, UidProto.CAMERA, u.getCameraTurnedOnTimer(), rawRealtimeUs, which);
+
+ // CPU stats (CPU_DATA & CPU_TIMES_AT_FREQ_DATA)
+ final long cpuToken = proto.start(UidProto.CPU);
+ proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
+ proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
+
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+ // If total cpuFreqTimes is null, then we don't need to check for
+ // screenOffCpuFreqTimes.
+ if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+ long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+ if (screenOffCpuFreqTimeMs == null) {
+ screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+ }
+ for (int ic = 0; ic < cpuFreqTimeMs.length; ++ic) {
+ long cToken = proto.start(UidProto.Cpu.BY_FREQUENCY);
+ proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+ proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+ cpuFreqTimeMs[ic]);
+ proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+ screenOffCpuFreqTimeMs[ic]);
+ proto.end(cToken);
+ }
+ }
+ }
+ proto.end(cpuToken);
+
+ // Flashlight (FLASHLIGHT_DATA)
+ dumpTimer(proto, UidProto.FLASHLIGHT, u.getFlashlightTurnedOnTimer(),
+ rawRealtimeUs, which);
+
+ // Foreground activity (FOREGROUND_ACTIVITY_DATA)
+ dumpTimer(proto, UidProto.FOREGROUND_ACTIVITY, u.getForegroundActivityTimer(),
+ rawRealtimeUs, which);
+
+ // Foreground service (FOREGROUND_SERVICE_DATA)
+ dumpTimer(proto, UidProto.FOREGROUND_SERVICE, u.getForegroundServiceTimer(),
+ rawRealtimeUs, which);
+
+ // Job completion (JOB_COMPLETION_DATA)
+ final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+ final int[] reasons = new int[]{
+ JobParameters.REASON_CANCELED,
+ JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+ JobParameters.REASON_PREEMPT,
+ JobParameters.REASON_TIMEOUT,
+ JobParameters.REASON_DEVICE_IDLE,
+ };
+ for (int ic = 0; ic < completions.size(); ++ic) {
+ SparseIntArray types = completions.valueAt(ic);
+ if (types != null) {
+ final long jcToken = proto.start(UidProto.JOB_COMPLETION);
+
+ proto.write(UidProto.JobCompletion.NAME, completions.keyAt(ic));
+
+ for (int r : reasons) {
+ long rToken = proto.start(UidProto.JobCompletion.REASON_COUNT);
+ proto.write(UidProto.JobCompletion.ReasonCount.NAME, r);
+ proto.write(UidProto.JobCompletion.ReasonCount.COUNT, types.get(r, 0));
+ proto.end(rToken);
+ }
+
+ proto.end(jcToken);
+ }
+ }
+
+ // Scheduled jobs (JOB_DATA)
+ final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+ for (int ij = jobs.size() - 1; ij >= 0; --ij) {
+ final Timer timer = jobs.valueAt(ij);
+ final Timer bgTimer = timer.getSubTimer();
+ final long jToken = proto.start(UidProto.JOBS);
+
+ proto.write(UidProto.Job.NAME, jobs.keyAt(ij));
+ // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+ dumpTimer(proto, UidProto.Job.TOTAL, timer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Job.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+ proto.end(jToken);
+ }
+
+ // Modem Controller (MODEM_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, UidProto.MODEM_CONTROLLER,
+ u.getModemControllerActivity(), which);
+
+ // Network stats (NETWORK_DATA)
+ final long nToken = proto.start(UidProto.NETWORK);
+ proto.write(UidProto.Network.MOBILE_BYTES_RX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_BYTES_TX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_RX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_TX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+ proto.write(UidProto.Network.BT_BYTES_RX,
+ u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+ proto.write(UidProto.Network.BT_BYTES_TX,
+ u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_RX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_TX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_RX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_TX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_ACTIVE_DURATION_MS,
+ roundUsToMs(u.getMobileRadioActiveTime(which)));
+ proto.write(UidProto.Network.MOBILE_ACTIVE_COUNT,
+ u.getMobileRadioActiveCount(which));
+ proto.write(UidProto.Network.MOBILE_WAKEUP_COUNT,
+ u.getMobileRadioApWakeupCount(which));
+ proto.write(UidProto.Network.WIFI_WAKEUP_COUNT,
+ u.getWifiRadioApWakeupCount(which));
+ proto.write(UidProto.Network.MOBILE_BYTES_BG_RX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_BYTES_BG_TX,
+ u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_BG_RX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_BYTES_BG_TX,
+ u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_BG_RX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA, which));
+ proto.write(UidProto.Network.MOBILE_PACKETS_BG_TX,
+ u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_BG_RX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA, which));
+ proto.write(UidProto.Network.WIFI_PACKETS_BG_TX,
+ u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA, which));
+ proto.end(nToken);
+
+ // Power use item (POWER_USE_ITEM_DATA)
+ BatterySipper bs = uidToSipper.get(uid);
+ if (bs != null) {
+ final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+ proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+ proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+ proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+ proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+ bs.proportionalSmearMah);
+ proto.end(bsToken);
+ }
+
+ // Processes (PROCESS_DATA)
+ final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats =
+ u.getProcessStats();
+ for (int ipr = processStats.size() - 1; ipr >= 0; --ipr) {
+ final Uid.Proc ps = processStats.valueAt(ipr);
+ final long prToken = proto.start(UidProto.PROCESS);
+
+ proto.write(UidProto.Process.NAME, processStats.keyAt(ipr));
+ proto.write(UidProto.Process.USER_DURATION_MS, ps.getUserTime(which));
+ proto.write(UidProto.Process.SYSTEM_DURATION_MS, ps.getSystemTime(which));
+ proto.write(UidProto.Process.FOREGROUND_DURATION_MS, ps.getForegroundTime(which));
+ proto.write(UidProto.Process.START_COUNT, ps.getStarts(which));
+ proto.write(UidProto.Process.ANR_COUNT, ps.getNumAnrs(which));
+ proto.write(UidProto.Process.CRASH_COUNT, ps.getNumCrashes(which));
+
+ proto.end(prToken);
+ }
+
+ // Sensors (SENSOR_DATA)
+ final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+ for (int ise = 0; ise < sensors.size(); ++ise) {
+ final Uid.Sensor se = sensors.valueAt(ise);
+ final Timer timer = se.getSensorTime();
+ if (timer == null) {
+ continue;
+ }
+ final Timer bgTimer = se.getSensorBackgroundTime();
+ final int sensorNumber = sensors.keyAt(ise);
+ final long seToken = proto.start(UidProto.SENSORS);
+
+ proto.write(UidProto.Sensor.ID, sensorNumber);
+ // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+ dumpTimer(proto, UidProto.Sensor.APPORTIONED, timer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Sensor.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+ proto.end(seToken);
+ }
+
+ // State times (STATE_TIME_DATA)
+ for (int ips = 0; ips < Uid.NUM_PROCESS_STATE; ++ips) {
+ long durMs = roundUsToMs(u.getProcessStateTime(ips, rawRealtimeUs, which));
+ if (durMs == 0) {
+ continue;
+ }
+ final long stToken = proto.start(UidProto.STATES);
+ proto.write(UidProto.StateTime.STATE, ips);
+ proto.write(UidProto.StateTime.DURATION_MS, durMs);
+ proto.end(stToken);
+ }
+
+ // Syncs (SYNC_DATA)
+ final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+ for (int isy = syncs.size() - 1; isy >= 0; --isy) {
+ final Timer timer = syncs.valueAt(isy);
+ final Timer bgTimer = timer.getSubTimer();
+ final long syToken = proto.start(UidProto.SYNCS);
+
+ proto.write(UidProto.Sync.NAME, syncs.keyAt(isy));
+ // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+ dumpTimer(proto, UidProto.Sync.TOTAL, timer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Sync.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+ proto.end(syToken);
+ }
+
+ // User activity (USER_ACTIVITY_DATA)
+ if (u.hasUserActivity()) {
+ for (int i = 0; i < Uid.NUM_USER_ACTIVITY_TYPES; ++i) {
+ int val = u.getUserActivityCount(i, which);
+ if (val != 0) {
+ final long uaToken = proto.start(UidProto.USER_ACTIVITY);
+ proto.write(UidProto.UserActivity.NAME, i);
+ proto.write(UidProto.UserActivity.COUNT, val);
+ proto.end(uaToken);
+ }
+ }
+ }
+
+ // Vibrator (VIBRATOR_DATA)
+ dumpTimer(proto, UidProto.VIBRATOR, u.getVibratorOnTimer(), rawRealtimeUs, which);
+
+ // Video (VIDEO_DATA)
+ dumpTimer(proto, UidProto.VIDEO, u.getVideoTurnedOnTimer(), rawRealtimeUs, which);
+
+ // Wakelocks (WAKELOCK_DATA)
+ final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+ for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+ final Uid.Wakelock wl = wakelocks.valueAt(iw);
+ final long wToken = proto.start(UidProto.WAKELOCKS);
+ proto.write(UidProto.Wakelock.NAME, wakelocks.keyAt(iw));
+ dumpTimer(proto, UidProto.Wakelock.FULL, wl.getWakeTime(WAKE_TYPE_FULL),
+ rawRealtimeUs, which);
+ final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+ if (pTimer != null) {
+ dumpTimer(proto, UidProto.Wakelock.PARTIAL, pTimer, rawRealtimeUs, which);
+ dumpTimer(proto, UidProto.Wakelock.BACKGROUND_PARTIAL, pTimer.getSubTimer(),
+ rawRealtimeUs, which);
+ }
+ dumpTimer(proto, UidProto.Wakelock.WINDOW, wl.getWakeTime(WAKE_TYPE_WINDOW),
+ rawRealtimeUs, which);
+ proto.end(wToken);
+ }
+
+ // Wakeup alarms (WAKEUP_ALARM_DATA)
+ for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+ final Uid.Pkg ps = packageStats.valueAt(ipkg);
+ final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+ for (int iwa = alarms.size() - 1; iwa >= 0; --iwa) {
+ final long waToken = proto.start(UidProto.WAKEUP_ALARM);
+ proto.write(UidProto.WakeupAlarm.NAME, alarms.keyAt(iwa));
+ proto.write(UidProto.WakeupAlarm.COUNT,
+ alarms.valueAt(iwa).getCountLocked(which));
+ proto.end(waToken);
+ }
+ }
+
+ // Wifi Controller (WIFI_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, UidProto.WIFI_CONTROLLER,
+ u.getWifiControllerActivity(), which);
+
+ // Wifi data (WIFI_DATA)
+ final long wToken = proto.start(UidProto.WIFI);
+ proto.write(UidProto.Wifi.FULL_WIFI_LOCK_DURATION_MS,
+ roundUsToMs(u.getFullWifiLockTime(rawRealtimeUs, which)));
+ dumpTimer(proto, UidProto.Wifi.APPORTIONED_SCAN, u.getWifiScanTimer(),
+ rawRealtimeUs, which);
+ proto.write(UidProto.Wifi.RUNNING_DURATION_MS,
+ roundUsToMs(u.getWifiRunningTime(rawRealtimeUs, which)));
+ dumpTimer(proto, UidProto.Wifi.BACKGROUND_SCAN, u.getWifiScanBackgroundTimer(),
+ rawRealtimeUs, which);
+ proto.end(wToken);
+
+ proto.end(uTkn);
+ }
+ }
+
+ private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
final long sToken = proto.start(BatteryStatsProto.SYSTEM);
final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
final long rawRealtimeMs = SystemClock.elapsedRealtime();
@@ -6637,7 +7088,7 @@
final int which = STATS_SINCE_CHARGED;
// Battery data (BATTERY_DATA)
- long token = proto.start(SystemProto.BATTERY);
+ final long bToken = proto.start(SystemProto.BATTERY);
proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
proto.write(SystemProto.Battery.START_COUNT, getStartCount());
proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
@@ -6660,10 +7111,10 @@
getMinLearnedBatteryCapacity());
proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
getMaxLearnedBatteryCapacity());
- proto.end(token);
+ proto.end(bToken);
// Battery discharge (BATTERY_DISCHARGE_DATA)
- token = proto.start(SystemProto.BATTERY_DISCHARGE);
+ final long bdToken = proto.start(SystemProto.BATTERY_DISCHARGE);
proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
getLowDischargeAmountSinceCharge());
proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
@@ -6680,10 +7131,11 @@
getUahDischargeScreenOff(which) / 1000);
proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
getUahDischargeScreenDoze(which) / 1000);
- proto.end(token);
+ proto.end(bdToken);
// Time remaining
long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+ // These are part of a oneof, so we should only set one of them.
if (timeRemainingUs >= 0) {
// Charge time remaining (CHARGE_TIME_REMAIN_DATA)
proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
@@ -6702,11 +7154,11 @@
// Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
- token = proto.start(SystemProto.DATA_CONNECTION);
+ final long pdcToken = proto.start(SystemProto.DATA_CONNECTION);
proto.write(SystemProto.DataConnection.NAME, i);
dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(pdcToken);
}
// Discharge step (DISCHARGE_STEP_DATA)
@@ -6729,7 +7181,7 @@
getModemControllerActivity(), which);
// Global network data (GLOBAL_NETWORK_DATA)
- token = proto.start(SystemProto.GLOBAL_NETWORK);
+ final long gnToken = proto.start(SystemProto.GLOBAL_NETWORK);
proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
@@ -6750,7 +7202,7 @@
getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
- proto.end(token);
+ proto.end(gnToken);
// Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
@@ -6758,21 +7210,21 @@
// Global wifi (GLOBAL_WIFI_DATA)
- token = proto.start(SystemProto.GLOBAL_WIFI);
+ final long gwToken = proto.start(SystemProto.GLOBAL_WIFI);
proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
getWifiOnTime(rawRealtimeUs, which) / 1000);
proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
- proto.end(token);
+ proto.end(gwToken);
// Kernel wakelock (KERNEL_WAKELOCK_DATA)
final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
- token = proto.start(SystemProto.KERNEL_WAKELOCK);
+ final long kwToken = proto.start(SystemProto.KERNEL_WAKELOCK);
proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(kwToken);
}
// Misc (MISC_DATA)
@@ -6802,7 +7254,7 @@
}
}
}
- token = proto.start(SystemProto.MISC);
+ final long mToken = proto.start(SystemProto.MISC);
proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
getScreenOnTime(rawRealtimeUs, which) / 1000);
proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
@@ -6845,11 +7297,7 @@
getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
- proto.end(token);
-
- final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
- helper.create(this);
- helper.refreshStats(which, UserHandle.USER_ALL);
+ proto.end(mToken);
// Power use item (POWER_USE_ITEM_DATA)
final List<BatterySipper> sippers = helper.getUsageList();
@@ -6881,7 +7329,7 @@
n = SystemProto.PowerUseItem.FLASHLIGHT;
break;
case APP:
- // dumpProtoAppLocked will handle this.
+ // dumpProtoAppsLocked will handle this.
continue;
case USER:
n = SystemProto.PowerUseItem.USER;
@@ -6900,7 +7348,7 @@
n = SystemProto.PowerUseItem.MEMORY;
break;
}
- token = proto.start(SystemProto.POWER_USE_ITEM);
+ final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
proto.write(SystemProto.PowerUseItem.NAME, n);
proto.write(SystemProto.PowerUseItem.UID, uid);
proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
@@ -6908,39 +7356,39 @@
proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
bs.proportionalSmearMah);
- proto.end(token);
+ proto.end(puiToken);
}
}
// Power use summary (POWER_USE_SUMMARY_DATA)
- token = proto.start(SystemProto.POWER_USE_SUMMARY);
+ final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
helper.getPowerProfile().getBatteryCapacity());
proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
- proto.end(token);
+ proto.end(pusToken);
// RPM stats (RESOURCE_POWER_MANAGER_DATA)
final Map<String, ? extends Timer> rpmStats = getRpmStats();
final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
- token = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+ final long rpmToken = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
ent.getValue(), rawRealtimeUs, which);
dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
- proto.end(token);
+ proto.end(rpmToken);
}
// Screen brightness (SCREEN_BRIGHTNESS_DATA)
for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
- token = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+ final long sbToken = proto.start(SystemProto.SCREEN_BRIGHTNESS);
proto.write(SystemProto.ScreenBrightness.NAME, i);
dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(sbToken);
}
// Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
@@ -6949,47 +7397,47 @@
// Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
- token = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+ final long pssToken = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
proto.write(SystemProto.PhoneSignalStrength.NAME, i);
dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(pssToken);
}
// Wakeup reasons (WAKEUP_REASON_DATA)
final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
- token = proto.start(SystemProto.WAKEUP_REASON);
+ final long wrToken = proto.start(SystemProto.WAKEUP_REASON);
proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
- proto.end(token);
+ proto.end(wrToken);
}
// Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
- token = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+ final long wssToken = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
proto.write(SystemProto.WifiSignalStrength.NAME, i);
dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(wssToken);
}
// Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
for (int i = 0; i < NUM_WIFI_STATES; ++i) {
- token = proto.start(SystemProto.WIFI_STATE);
+ final long wsToken = proto.start(SystemProto.WIFI_STATE);
proto.write(SystemProto.WifiState.NAME, i);
dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(wsToken);
}
// Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
- token = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+ final long wssToken = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
proto.write(SystemProto.WifiSupplicantState.NAME, i);
dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
rawRealtimeUs, which);
- proto.end(token);
+ proto.end(wssToken);
}
proto.end(sToken);
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index d8f9567..20f6c8e 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -16,6 +16,8 @@
package android.os;
+import android.os.StatsLogEventWrapper;
+
/**
* Binder interface to communicate with the Java-based statistics service helper.
* {@hide}
@@ -50,5 +52,5 @@
oneway void cancelPollingAlarms();
/** Pull the specified data. Results will be sent to statsd when complete. */
- String pullData(int pullCode);
+ StatsLogEventWrapper[] pullData(int pullCode);
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/os/StatsLogEventWrapper.aidl
similarity index 67%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/os/StatsLogEventWrapper.aidl
index 7294124..766343e 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/os/StatsLogEventWrapper.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * 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.
@@ -14,12 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.os;
-import android.os.IBinder;
-
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
-}
+/** @hide */
+parcelable StatsLogEventWrapper cpp_header "android/os/StatsLogEventWrapper.h";
\ No newline at end of file
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
new file mode 100644
index 0000000..9491bec
--- /dev/null
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,141 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+ private ByteArrayOutputStream mStorage = new ByteArrayOutputStream();
+
+ // Below are constants copied from log/log.h
+ private static final int EVENT_TYPE_INT = 0; /* int32_t */
+ private static final int EVENT_TYPE_LONG = 1; /* int64_t */
+ private static final int EVENT_TYPE_STRING = 2;
+ private static final int EVENT_TYPE_LIST = 3;
+ private static final int EVENT_TYPE_FLOAT = 4;
+
+ /**
+ * Creates a log_event that is binary-encoded as implemented in
+ * system/core/liblog/log_event_list.c; this allows us to use the same parsing logic in statsd
+ * for pushed and pulled data. The write* methods must be called in the same order as their
+ * field number. There is no checking that the correct number of write* methods is called.
+ * We also write an END_LIST character before beginning to write to parcel, but this END_LIST
+ * may be unnecessary.
+ *
+ * @param tag The integer representing the tag for this event.
+ * @param fields The number of fields specified in this event.
+ */
+ public StatsLogEventWrapper(int tag, int fields) {
+ // Write four bytes from tag, starting with least-significant bit.
+ write4Bytes(tag);
+ mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
+ mStorage.write(fields); // Indicate number of elements in this list.
+ }
+
+ /**
+ * Boilerplate for Parcel.
+ */
+ public static final Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+ Parcelable.Creator<StatsLogEventWrapper>() {
+ public StatsLogEventWrapper createFromParcel(Parcel in) {
+ return new StatsLogEventWrapper(in);
+ }
+
+ public StatsLogEventWrapper[] newArray(int size) {
+ return new StatsLogEventWrapper[size];
+ }
+ };
+
+ private void write4Bytes(int val) {
+ mStorage.write(val);
+ mStorage.write(val >>> 8);
+ mStorage.write(val >>> 16);
+ mStorage.write(val >>> 24);
+ }
+
+ private void write8Bytes(long val) {
+ write4Bytes((int) (val & 0xFFFFFFFF)); // keep the lowe 32-bits
+ write4Bytes((int) (val >>> 32)); // Write the high 32-bits.
+ }
+
+ /**
+ * Adds 32-bit integer to output.
+ */
+ public void writeInt(int val) {
+ mStorage.write(EVENT_TYPE_INT);
+ write4Bytes(val);
+ }
+
+ /**
+ * Adds 64-bit long to output.
+ */
+ public void writeLong(long val) {
+ mStorage.write(EVENT_TYPE_LONG);
+ write8Bytes(val);
+ }
+
+ /**
+ * Adds a 4-byte floating point value to output.
+ */
+ public void writeFloat(float val) {
+ int v = Float.floatToIntBits(val);
+ mStorage.write(EVENT_TYPE_FLOAT);
+ write4Bytes(v);
+ }
+
+ /**
+ * Adds a string to the output.
+ */
+ public void writeString(String val) {
+ mStorage.write(EVENT_TYPE_STRING);
+ write4Bytes(val.length());
+ byte[] bytes = val.getBytes(StandardCharsets.UTF_8);
+ mStorage.write(bytes, 0, bytes.length);
+ }
+
+ private StatsLogEventWrapper(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Writes the stored fields to a byte array. Will first write a new-line character to denote
+ * END_LIST before writing contents to byte array.
+ */
+ public void writeToParcel(Parcel out, int flags) {
+ mStorage.write(10); // new-line character is same as END_LIST
+ out.writeByteArray(mStorage.toByteArray());
+ }
+
+ /**
+ * Not implemented.
+ */
+ public void readFromParcel(Parcel in) {
+ // Not needed since this java class is for sending to statsd only.
+ }
+
+ /**
+ * Boilerplate for Parcel.
+ */
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b435074..c200ae7 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -442,6 +442,18 @@
"android.settings.ASSIST_GESTURE_SETTINGS";
/**
+ * Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
+ * necessary.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_FINGERPRINT_ENROLL =
+ "android.settings.FINGERPRINT_ENROLL";
+
+ /**
* Activity Action: Show settings to allow configuration of cast endpoints.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -9254,6 +9266,13 @@
*/
public static final String DEFAULT_DNS_SERVER = "default_dns_server";
+ /**
+ * Whether to disable DNS over TLS (boolean)
+ *
+ * @hide
+ */
+ public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+
/** {@hide} */
public static final String
BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
index 8fcd5ab..79115a5 100644
--- a/core/java/android/security/net/config/ManifestConfigSource.java
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -20,6 +20,7 @@
import android.content.pm.ApplicationInfo;
import android.util.Log;
import android.util.Pair;
+
import java.util.Set;
/** @hide */
@@ -29,21 +30,14 @@
private final Object mLock = new Object();
private final Context mContext;
- private final int mApplicationInfoFlags;
- private final int mTargetSdkVersion;
- private final int mConfigResourceId;
- private final int mTargetSandboxVesrsion;
+ private final ApplicationInfo mApplicationInfo;
private ConfigSource mConfigSource;
public ManifestConfigSource(Context context) {
mContext = context;
- // Cache values because ApplicationInfo is mutable and apps do modify it :(
- ApplicationInfo info = context.getApplicationInfo();
- mApplicationInfoFlags = info.flags;
- mTargetSdkVersion = info.targetSdkVersion;
- mConfigResourceId = info.networkSecurityConfigRes;
- mTargetSandboxVesrsion = info.targetSandboxVersion;
+ // Cache the info because ApplicationInfo is mutable and apps do modify it :(
+ mApplicationInfo = new ApplicationInfo(context.getApplicationInfo());
}
@Override
@@ -61,17 +55,18 @@
if (mConfigSource != null) {
return mConfigSource;
}
-
+ int configResource = mApplicationInfo.networkSecurityConfigRes;
ConfigSource source;
- if (mConfigResourceId != 0) {
- boolean debugBuild = (mApplicationInfoFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (configResource != 0) {
+ boolean debugBuild =
+ (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
if (DBG) {
Log.d(LOG_TAG, "Using Network Security Config from resource "
- + mContext.getResources().getResourceEntryName(mConfigResourceId)
+ + mContext.getResources()
+ .getResourceEntryName(configResource)
+ " debugBuild: " + debugBuild);
}
- source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild,
- mTargetSdkVersion, mTargetSandboxVesrsion);
+ source = new XmlConfigSource(mContext, configResource, mApplicationInfo);
} else {
if (DBG) {
Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
@@ -79,10 +74,9 @@
// the legacy FLAG_USES_CLEARTEXT_TRAFFIC is not supported for Ephemeral apps, they
// should use the network security config.
boolean usesCleartextTraffic =
- (mApplicationInfoFlags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
- && mTargetSandboxVesrsion < 2;
- source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion,
- mTargetSandboxVesrsion);
+ (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
+ && mApplicationInfo.targetSandboxVersion < 2;
+ source = new DefaultConfigSource(usesCleartextTraffic, mApplicationInfo);
}
mConfigSource = source;
return mConfigSource;
@@ -93,10 +87,8 @@
private final NetworkSecurityConfig mDefaultConfig;
- public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion,
- int targetSandboxVesrsion) {
- mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion,
- targetSandboxVesrsion)
+ DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) {
+ mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info)
.setCleartextTrafficPermitted(usesCleartextTraffic)
.build();
}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 789fc27..b9e5505 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -16,9 +16,11 @@
package android.security.net.config;
+import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
+
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
@@ -28,8 +30,6 @@
import java.util.Map;
import java.util.Set;
-import javax.net.ssl.X509TrustManager;
-
/**
* @hide
*/
@@ -170,22 +170,24 @@
* <li>No certificate pinning is used.</li>
* <li>The system certificate store is trusted for connections.</li>
* <li>If the application targets API level 23 (Android M) or lower then the user certificate
- * store is trusted by default as well.</li>
+ * store is trusted by default as well for non-privileged applications.</li>
+ * <li>Privileged applications do not trust the user certificate store on Android P and higher.
+ * </li>
* </ol>
*
* @hide
*/
- public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) {
+ public static Builder getDefaultBuilder(ApplicationInfo info) {
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
- final boolean cleartextTrafficPermitted = targetSandboxVesrsion < 2;
+ final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
- if (targetSdkVersion <= Build.VERSION_CODES.M) {
+ if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
// User certificate store, does not bypass static pins.
builder.addCertificatesEntryRef(
new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index a111fbce..02be403 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -1,13 +1,13 @@
package android.security.net.config;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.os.Build;
import android.util.ArraySet;
import android.util.Base64;
import android.util.Pair;
-import com.android.internal.annotations.VisibleForTesting;
+
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -36,37 +36,19 @@
private final Object mLock = new Object();
private final int mResourceId;
private final boolean mDebugBuild;
- private final int mTargetSdkVersion;
- private final int mTargetSandboxVesrsion;
+ private final ApplicationInfo mApplicationInfo;
private boolean mInitialized;
private NetworkSecurityConfig mDefaultConfig;
private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
private Context mContext;
- @VisibleForTesting
- public XmlConfigSource(Context context, int resourceId) {
- this(context, resourceId, false);
- }
-
- @VisibleForTesting
- public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
- this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT);
- }
-
- @VisibleForTesting
- public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
- int targetSdkVersion) {
- this(context, resourceId, debugBuild, targetSdkVersion, 1 /*targetSandboxVersion*/);
- }
-
- public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
- int targetSdkVersion, int targetSandboxVesrsion) {
- mResourceId = resourceId;
+ public XmlConfigSource(Context context, int resourceId, ApplicationInfo info) {
mContext = context;
- mDebugBuild = debugBuild;
- mTargetSdkVersion = targetSdkVersion;
- mTargetSandboxVesrsion = targetSandboxVesrsion;
+ mResourceId = resourceId;
+ mApplicationInfo = new ApplicationInfo(info);
+
+ mDebugBuild = (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
@@ -365,7 +347,7 @@
// Use the platform default as the parent of the base config for any values not provided
// there. If there is no base config use the platform default.
NetworkSecurityConfig.Builder platformDefaultBuilder =
- NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion, mTargetSandboxVesrsion);
+ NetworkSecurityConfig.getDefaultBuilder(mApplicationInfo);
addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
if (baseConfigBuilder != null) {
baseConfigBuilder.setParent(platformDefaultBuilder);
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index ef9598a..331130e 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -150,8 +150,16 @@
public String toString() {
if (!sDebug) return super.toString();
- return new StringBuilder("Dataset " + mId + " [")
- .append("fieldIds=").append(mFieldIds)
+ final StringBuilder builder = new StringBuilder("Dataset[id=");
+ if (mId == null) {
+ builder.append("null");
+ } else {
+ // Cannot disclose id because it could contain PII.
+ builder.append(mId.length()).append("_chars");
+ }
+
+ return builder
+ .append(", fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
.append(", fieldPresentations=")
.append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
@@ -201,7 +209,8 @@
* Creates a new builder for a dataset where each field will be visualized independently.
*
* <p>When using this constructor, fields must be set through
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+ * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
*/
public Builder() {
}
@@ -280,19 +289,24 @@
/**
* Sets the value of a field.
*
+ * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
+ * throw an {@link IllegalStateException} if this builder was constructed without a
+ * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
+ * higher removed this restriction because datasets used as an
+ * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
+ * authentication result} do not need a presentation. But if you don't set the presentation
+ * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
+ * for this field will not be displayed.
+ *
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
* @param value value to be autofilled. Pass {@code null} if you do not have the value
* but the target view is a logical part of the dataset. For example, if
* the dataset needs authentication and you have no access to the value.
* @return this builder.
- * @throws IllegalStateException if the builder was constructed without a
- * {@link RemoteViews presentation}.
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
- Preconditions.checkState(mPresentation != null,
- "Dataset presentation not set on constructor");
setLifeTheUniverseAndEverything(id, value, null, null);
return this;
}
@@ -334,7 +348,7 @@
*
* @return this builder.
* @throws IllegalStateException if the builder was constructed without a
- * {@link RemoteViews presentation}.
+ * {@link RemoteViews presentation}.
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull Pattern filter) {
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 3b719ac..b1857b3 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -17,19 +17,27 @@
package android.service.autofill;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Describes what happened after the last
@@ -121,6 +129,11 @@
}
@Override
+ public String toString() {
+ return mEvents == null ? "no events" : mEvents.toString();
+ }
+
+ @Override
public int describeContents() {
return 0;
}
@@ -136,9 +149,21 @@
int numEvents = mEvents.size();
for (int i = 0; i < numEvents; i++) {
Event event = mEvents.get(i);
- dest.writeInt(event.getType());
- dest.writeString(event.getDatasetId());
- dest.writeBundle(event.getClientState());
+ dest.writeInt(event.mEventType);
+ dest.writeString(event.mDatasetId);
+ dest.writeBundle(event.mClientState);
+ dest.writeStringList(event.mSelectedDatasetIds);
+ dest.writeArraySet(event.mIgnoredDatasetIds);
+ dest.writeTypedList(event.mChangedFieldIds);
+ dest.writeStringList(event.mChangedDatasetIds);
+
+ dest.writeTypedList(event.mManuallyFilledFieldIds);
+ if (event.mManuallyFilledFieldIds != null) {
+ final int size = event.mManuallyFilledFieldIds.size();
+ for (int j = 0; j < size; j++) {
+ dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
+ }
+ }
}
}
}
@@ -176,12 +201,40 @@
/** A save UI was shown. */
public static final int TYPE_SAVE_SHOWN = 3;
+ /**
+ * A committed autofill context for which the autofill service provided datasets.
+ *
+ * <p>This event is useful to track:
+ * <ul>
+ * <li>Which datasets (if any) were selected by the user
+ * ({@link #getSelectedDatasetIds()}).
+ * <li>Which datasets (if any) were NOT selected by the user
+ * ({@link #getIgnoredDatasetIds()}).
+ * <li>Which fields in the selected datasets were changed by the user after the dataset
+ * was selected ({@link #getChangedFields()}.
+ * </ul>
+ *
+ * <p><b>Note: </b>This event is only generated when:
+ * <ul>
+ * <li>The autofill context is committed.
+ * <li>The service provides at least one dataset in the
+ * {@link FillResponse fill responses} associated with the context.
+ * <li>The last {@link FillResponse fill responses} associated with the context has the
+ * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
+ * </ul>
+ *
+ * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
+ * contexts.
+ */
+ public static final int TYPE_CONTEXT_COMMITTED = 4;
+
/** @hide */
@IntDef(
value = {TYPE_DATASET_SELECTED,
TYPE_DATASET_AUTHENTICATION_SELECTED,
TYPE_AUTHENTICATION_SELECTED,
- TYPE_SAVE_SHOWN})
+ TYPE_SAVE_SHOWN,
+ TYPE_CONTEXT_COMMITTED})
@Retention(RetentionPolicy.SOURCE)
@interface EventIds{}
@@ -189,6 +242,17 @@
@Nullable private final String mDatasetId;
@Nullable private final Bundle mClientState;
+ // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
+ // stores it as List
+ @Nullable private final List<String> mSelectedDatasetIds;
+ @Nullable private final ArraySet<String> mIgnoredDatasetIds;
+
+ @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
+ @Nullable private final ArrayList<String> mChangedDatasetIds;
+
+ @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
+ @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+
/**
* Returns the type of the event.
*
@@ -220,25 +284,202 @@
}
/**
+ * Returns which datasets were selected by the user.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ */
+ @NonNull public Set<String> getSelectedDatasetIds() {
+ return mSelectedDatasetIds == null ? Collections.emptySet()
+ : new ArraySet<>(mSelectedDatasetIds);
+ }
+
+ /**
+ * Returns which datasets were NOT selected by the user.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ */
+ @NonNull public Set<String> getIgnoredDatasetIds() {
+ return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
+ }
+
+ /**
+ * Returns which fields in the selected datasets were changed by the user after the dataset
+ * was selected.
+ *
+ * <p>For example, server provides:
+ *
+ * <pre class="prettyprint">
+ * FillResponse response = new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder(presentation1)
+ * .setId("4815")
+ * .setValue(usernameId, AutofillValue.forText("MrPlow"))
+ * .build())
+ * .addDataset(new Dataset.Builder(presentation2)
+ * .setId("162342")
+ * .setValue(passwordId, AutofillValue.forText("D'OH"))
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>User select both datasets (for username and password) but after the fields are
+ * autofilled, user changes them to:
+ *
+ * <pre class="prettyprint">
+ * username = "ElBarto";
+ * password = "AyCaramba";
+ * </pre>
+ *
+ * <p>Then the result is the following map:
+ *
+ * <pre class="prettyprint">
+ * usernameId => "4815"
+ * passwordId => "162342"
+ * </pre>
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ *
+ * @return map map whose key is the id of the change fields, and value is the id of
+ * dataset that has that field and was selected by the user.
+ */
+ @NonNull public Map<AutofillId, String> getChangedFields() {
+ if (mChangedFieldIds == null || mChangedDatasetIds == null) {
+ return Collections.emptyMap();
+ }
+
+ final int size = mChangedFieldIds.size();
+ final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
+ }
+ return changedFields;
+ }
+
+ /**
+ * Returns which fields were available on datasets provided by the service but manually
+ * entered by the user.
+ *
+ * <p>For example, server provides:
+ *
+ * <pre class="prettyprint">
+ * FillResponse response = new FillResponse.Builder()
+ * .addDataset(new Dataset.Builder(presentation1)
+ * .setId("4815")
+ * .setValue(usernameId, AutofillValue.forText("MrPlow"))
+ * .setValue(passwordId, AutofillValue.forText("AyCaramba"))
+ * .build())
+ * .addDataset(new Dataset.Builder(presentation2)
+ * .setId("162342")
+ * .setValue(usernameId, AutofillValue.forText("ElBarto"))
+ * .setValue(passwordId, AutofillValue.forText("D'OH"))
+ * .build())
+ * .addDataset(new Dataset.Builder(presentation3)
+ * .setId("108")
+ * .setValue(usernameId, AutofillValue.forText("MrPlow"))
+ * .setValue(passwordId, AutofillValue.forText("D'OH"))
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <p>User doesn't select a dataset but manually enters:
+ *
+ * <pre class="prettyprint">
+ * username = "MrPlow";
+ * password = "D'OH";
+ * </pre>
+ *
+ * <p>Then the result is the following map:
+ *
+ * <pre class="prettyprint">
+ * usernameId => { "4815", "108"}
+ * passwordId => { "162342", "108" }
+ * </pre>
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+ *
+ * @return map map whose key is the id of the manually-entered field, and value is the
+ * ids of the datasets that have that value but were not selected by the user.
+ */
+ @Nullable public Map<AutofillId, Set<String>> getManuallyEnteredField() {
+ if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
+ return Collections.emptyMap();
+ }
+
+ final int size = mManuallyFilledFieldIds.size();
+ final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
+ final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
+ manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
+ }
+ return manuallyFilledFields;
+ }
+
+ /**
* Creates a new event.
*
* @param eventType The type of the event
* @param datasetId The dataset the event was on, or {@code null} if the event was on the
* whole response.
* @param clientState The client state associated with the event.
+ * @param selectedDatasetIds The ids of datasets selected by the user.
+ * @param ignoredDatasetIds The ids of datasets NOT select by the user.
+ * @param changedFieldIds The ids of fields changed by the user.
+ * @param changedDatasetIds The ids of the datasets that havd values matching the
+ * respective entry on {@code changedFieldIds}.
+ * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
+ * and belonged to datasets.
+ * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
+ * respective entry on {@code manuallyFilledFieldIds}.
+ *
+ * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
+ * {@code changedDatasetIds} doesn't match.
+ * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
+ * {@code manuallyFilledDatasetIds} doesn't match.
*
* @hide
*/
- public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) {
- mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+ public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
+ @Nullable List<String> selectedDatasetIds,
+ @Nullable ArraySet<String> ignoredDatasetIds,
+ @Nullable ArrayList<AutofillId> changedFieldIds,
+ @Nullable ArrayList<String> changedDatasetIds,
+ @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+ @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+ mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
"eventType");
mDatasetId = datasetId;
mClientState = clientState;
+ mSelectedDatasetIds = selectedDatasetIds;
+ mIgnoredDatasetIds = ignoredDatasetIds;
+ if (changedFieldIds != null) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
+ && changedDatasetIds != null
+ && changedFieldIds.size() == changedDatasetIds.size(),
+ "changed ids must have same length and not be empty");
+ }
+ mChangedFieldIds = changedFieldIds;
+ mChangedDatasetIds = changedDatasetIds;
+ if (manuallyFilledFieldIds != null) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
+ && manuallyFilledDatasetIds != null
+ && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
+ "manually filled ids must have same length and not be empty");
+ }
+ mManuallyFilledFieldIds = manuallyFilledFieldIds;
+ mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
}
@Override
public String toString() {
- return "FillEvent [datasetId=" + mDatasetId + ", type=" + mEventType + "]";
+ return "FillEvent [datasetId=" + mDatasetId
+ + ", type=" + mEventType
+ + ", selectedDatasets=" + mSelectedDatasetIds
+ + ", ignoredDatasetIds=" + mIgnoredDatasetIds
+ + ", changedFieldIds=" + mChangedFieldIds
+ + ", changedDatasetsIds=" + mChangedDatasetIds
+ + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+ + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+ + "]";
}
}
@@ -248,12 +489,37 @@
public FillEventHistory createFromParcel(Parcel parcel) {
FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
- int numEvents = parcel.readInt();
+ final int numEvents = parcel.readInt();
for (int i = 0; i < numEvents; i++) {
- selection.addEvent(new Event(parcel.readInt(), parcel.readString(),
- parcel.readBundle()));
- }
+ final int eventType = parcel.readInt();
+ final String datasetId = parcel.readString();
+ final Bundle clientState = parcel.readBundle();
+ final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
+ @SuppressWarnings("unchecked")
+ final ArraySet<String> ignoredDatasets =
+ (ArraySet<String>) parcel.readArraySet(null);
+ final ArrayList<AutofillId> changedFieldIds =
+ parcel.createTypedArrayList(AutofillId.CREATOR);
+ final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
+ final ArrayList<AutofillId> manuallyFilledFieldIds =
+ parcel.createTypedArrayList(AutofillId.CREATOR);
+ final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
+ if (manuallyFilledFieldIds != null) {
+ final int size = manuallyFilledFieldIds.size();
+ manuallyFilledDatasetIds = new ArrayList<>(size);
+ for (int j = 0; j < size; j++) {
+ manuallyFilledDatasetIds.add(parcel.createStringArrayList());
+ }
+ } else {
+ manuallyFilledDatasetIds = null;
+ }
+
+ selection.addEvent(new Event(eventType, datasetId, clientState,
+ selectedDatasetIds, ignoredDatasets,
+ changedFieldIds, changedDatasetIds,
+ manuallyFilledFieldIds, manuallyFilledDatasetIds));
+ }
return selection;
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 6d8a959..d2033fa 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -19,6 +19,7 @@
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.Helper.sDebug;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -30,6 +31,8 @@
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -42,6 +45,19 @@
*/
public final class FillResponse implements Parcelable {
+ /**
+ * Must be set in the last response to generate
+ * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} events.
+ */
+ public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FLAG_TRACK_CONTEXT_COMMITED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface FillResponseFlags {}
+
private final @Nullable ParceledListSlice<Dataset> mDatasets;
private final @Nullable SaveInfo mSaveInfo;
private final @Nullable Bundle mClientState;
@@ -49,16 +65,18 @@
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
+ private final int mFlags;
private int mRequestId;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
mSaveInfo = builder.mSaveInfo;
- mClientState = builder.mCLientState;
+ mClientState = builder.mClientState;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
+ mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -97,6 +115,11 @@
return mIgnoredIds;
}
+ /** @hide */
+ public int getFlags() {
+ return mFlags;
+ }
+
/**
* Associates a {@link FillResponse} to a request.
*
@@ -122,11 +145,12 @@
public static final class Builder {
private ArrayList<Dataset> mDatasets;
private SaveInfo mSaveInfo;
- private Bundle mCLientState;
+ private Bundle mClientState;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
+ private int mFlags;
private boolean mDestroyed;
/**
@@ -264,7 +288,20 @@
*/
public Builder setClientState(@Nullable Bundle clientState) {
throwIfDestroyed();
- mCLientState = clientState;
+ mClientState = clientState;
+ return this;
+ }
+
+ /**
+ * Sets flags changing the response behavior.
+ *
+ * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+ *
+ * @return This builder.
+ */
+ public Builder setFlags(@FillResponseFlags int flags) {
+ throwIfDestroyed();
+ mFlags = flags;
return this;
}
@@ -311,6 +348,7 @@
.append(", hasAuthentication=").append(mAuthentication != null)
.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+ .append(", flags=").append(mFlags)
.append("]")
.toString();
}
@@ -333,6 +371,7 @@
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
+ parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -363,8 +402,9 @@
}
builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
- final FillResponse response = builder.build();
+ builder.setFlags(parcel.readInt());
+ final FillResponse response = builder.build();
response.setRequestId(parcel.readInt());
return response;
diff --git a/core/java/android/service/autofill/LuhnChecksumValidator.java b/core/java/android/service/autofill/LuhnChecksumValidator.java
index 0b5930d..c56ae84 100644
--- a/core/java/android/service/autofill/LuhnChecksumValidator.java
+++ b/core/java/android/service/autofill/LuhnChecksumValidator.java
@@ -27,6 +27,8 @@
import com.android.internal.util.Preconditions;
+import java.util.Arrays;
+
/**
* Validator that returns {@code true} if the number created by concatenating all given fields
* pass a Luhn algorithm checksum. All non-digits are ignored.
@@ -86,17 +88,27 @@
public boolean isValid(@NonNull ValueFinder finder) {
if (mIds == null || mIds.length == 0) return false;
- final StringBuilder number = new StringBuilder();
+ final StringBuilder builder = new StringBuilder();
for (AutofillId id : mIds) {
final String partialNumber = finder.findByAutofillId(id);
if (partialNumber == null) {
if (sDebug) Log.d(TAG, "No partial number for id " + id);
return false;
}
- number.append(partialNumber);
+ builder.append(partialNumber);
}
- return isLuhnChecksumValid(number.toString());
+ final String number = builder.toString();
+ boolean valid = isLuhnChecksumValid(number);
+ if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
+ return valid;
+ }
+
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
}
/////////////////////////////////////
diff --git a/core/java/android/service/autofill/OptionalValidators.java b/core/java/android/service/autofill/OptionalValidators.java
index f7edd6e..7aec59f 100644
--- a/core/java/android/service/autofill/OptionalValidators.java
+++ b/core/java/android/service/autofill/OptionalValidators.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -34,6 +35,8 @@
*/
final class OptionalValidators extends InternalValidator {
+ private static final String TAG = "OptionalValidators";
+
@NonNull private final InternalValidator[] mValidators;
OptionalValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
public boolean isValid(@NonNull ValueFinder finder) {
for (InternalValidator validator : mValidators) {
final boolean valid = validator.isValid(finder);
+ if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
if (valid) return true;
}
diff --git a/core/java/android/service/autofill/RequiredValidators.java b/core/java/android/service/autofill/RequiredValidators.java
index ac85c28..9e1db2b 100644
--- a/core/java/android/service/autofill/RequiredValidators.java
+++ b/core/java/android/service/autofill/RequiredValidators.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -34,6 +35,8 @@
*/
final class RequiredValidators extends InternalValidator {
+ private static final String TAG = "RequiredValidators";
+
@NonNull private final InternalValidator[] mValidators;
RequiredValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
public boolean isValid(@NonNull ValueFinder finder) {
for (InternalValidator validator : mValidators) {
final boolean valid = validator.isValid(finder);
+ if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
if (!valid) return false;
}
return true;
diff --git a/core/java/android/service/settings/suggestions/ISuggestionService.aidl b/core/java/android/service/settings/suggestions/ISuggestionService.aidl
index 423c507..8dfa9c3 100644
--- a/core/java/android/service/settings/suggestions/ISuggestionService.aidl
+++ b/core/java/android/service/settings/suggestions/ISuggestionService.aidl
@@ -17,4 +17,10 @@
* calls.
*/
void dismissSuggestion(in Suggestion suggestion) = 2;
+
+ /**
+ * This is the opposite signal to {@link #dismissSuggestion}, indicating a suggestion has been
+ * launched.
+ */
+ void launchSuggestion(in Suggestion suggestion) = 3;
}
\ No newline at end of file
diff --git a/core/java/android/service/settings/suggestions/SuggestionService.java b/core/java/android/service/settings/suggestions/SuggestionService.java
index 2a4c84c..ce9501d 100644
--- a/core/java/android/service/settings/suggestions/SuggestionService.java
+++ b/core/java/android/service/settings/suggestions/SuggestionService.java
@@ -48,12 +48,20 @@
}
@Override
- public void dismissSuggestion(Suggestion suggestion) {
+ public void dismissSuggestion(Suggestion suggestion) {
if (DEBUG) {
Log.d(TAG, "dismissSuggestion() " + getPackageName());
}
onSuggestionDismissed(suggestion);
}
+
+ @Override
+ public void launchSuggestion(Suggestion suggestion) {
+ if (DEBUG) {
+ Log.d(TAG, "launchSuggestion() " + getPackageName());
+ }
+ onSuggestionLaunched(suggestion);
+ }
};
}
@@ -65,7 +73,12 @@
/**
* Dismiss a suggestion. The suggestion will not be included in future
* {@link #onGetSuggestions()} calls.
- * @param suggestion
*/
public abstract void onSuggestionDismissed(Suggestion suggestion);
+
+ /**
+ * This is the opposite signal to {@link #onSuggestionDismissed}, indicating a suggestion has
+ * been launched.
+ */
+ public abstract void onSuggestionLaunched(Suggestion suggestion);
}
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index bbe1523..179d545 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -16,6 +16,11 @@
package android.text;
+import android.icu.lang.UCharacter;
+import android.icu.lang.UCharacterDirection;
+import android.icu.lang.UProperty;
+import android.icu.text.Bidi;
+import android.icu.text.BidiClassifier;
import android.text.Layout.Directions;
import com.android.internal.annotations.VisibleForTesting;
@@ -27,26 +32,57 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class AndroidBidi {
- public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+ private static class EmojiBidiOverride extends BidiClassifier {
+ EmojiBidiOverride() {
+ super(null /* No persisting object needed */);
+ }
+
+ // Tells ICU to use the standard Unicode value.
+ private static final int NO_OVERRIDE =
+ UCharacter.getIntPropertyMaxValue(UProperty.BIDI_CLASS) + 1;
+
+ @Override
+ public int classify(int c) {
+ if (Emoji.isNewEmoji(c)) {
+ // All new emoji characters in Unicode 10.0 are of the bidi class ON.
+ return UCharacterDirection.OTHER_NEUTRAL;
+ } else {
+ return NO_OVERRIDE;
+ }
+ }
+ }
+
+ private static final EmojiBidiOverride sEmojiBidiOverride = new EmojiBidiOverride();
+
+ /**
+ * Runs the bidi algorithm on input text.
+ */
+ public static int bidi(int dir, char[] chs, byte[] chInfo) {
if (chs == null || chInfo == null) {
throw new NullPointerException();
}
- if (n < 0 || chs.length < n || chInfo.length < n) {
+ final int length = chs.length;
+ if (chInfo.length < length) {
throw new IndexOutOfBoundsException();
}
- switch(dir) {
- case Layout.DIR_REQUEST_LTR: dir = 0; break;
- case Layout.DIR_REQUEST_RTL: dir = 1; break;
- case Layout.DIR_REQUEST_DEFAULT_LTR: dir = -2; break;
- case Layout.DIR_REQUEST_DEFAULT_RTL: dir = -1; break;
- default: dir = 0; break;
+ final byte paraLevel;
+ switch (dir) {
+ case Layout.DIR_REQUEST_LTR: paraLevel = Bidi.LTR; break;
+ case Layout.DIR_REQUEST_RTL: paraLevel = Bidi.RTL; break;
+ case Layout.DIR_REQUEST_DEFAULT_LTR: paraLevel = Bidi.LEVEL_DEFAULT_LTR; break;
+ case Layout.DIR_REQUEST_DEFAULT_RTL: paraLevel = Bidi.LEVEL_DEFAULT_RTL; break;
+ default: paraLevel = Bidi.LTR; break;
}
-
- int result = runBidi(dir, chs, chInfo, n, haveInfo);
- result = (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
- return result;
+ final Bidi icuBidi = new Bidi(length /* maxLength */, 0 /* maxRunCount */);
+ icuBidi.setCustomClassifier(sEmojiBidiOverride);
+ icuBidi.setPara(chs, paraLevel, null /* embeddingLevels */);
+ for (int i = 0; i < length; i++) {
+ chInfo[i] = icuBidi.getLevelAt(i);
+ }
+ final byte result = icuBidi.getParaLevel();
+ return (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
}
/**
@@ -178,6 +214,4 @@
}
return new Directions(ld);
}
-
- private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
}
\ No newline at end of file
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index b09ccc2..ffc44a7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -106,8 +106,8 @@
if (mWidths == null || mWidths.length < len) {
mWidths = ArrayUtils.newUnpaddedFloatArray(len);
}
- if (mChars == null || mChars.length < len) {
- mChars = ArrayUtils.newUnpaddedCharArray(len);
+ if (mChars == null || mChars.length != len) {
+ mChars = new char[len];
}
TextUtils.getChars(text, start, end, mChars, 0);
@@ -151,7 +151,7 @@
boolean isRtl = textDir.isRtl(mChars, 0, len);
bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
}
- mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+ mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels);
mEasy = false;
}
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableBoolean.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableBoolean.java
index 7294124..ed837ab 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableBoolean.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.util;
-import android.os.IBinder;
+/**
+ */
+public final class MutableBoolean {
+ public boolean value;
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
+ public MutableBoolean(boolean value) {
+ this.value = value;
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableByte.java
similarity index 74%
rename from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
rename to core/java/android/util/MutableByte.java
index 7294124..cc6b00a 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableByte.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.util;
-import android.os.IBinder;
+/**
+ */
+public final class MutableByte {
+ public byte value;
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
+ public MutableByte(byte value) {
+ this.value = value;
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableChar.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableChar.java
index 7294124..9a2e2bc 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableChar.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.util;
-import android.os.IBinder;
+/**
+ */
+public final class MutableChar {
+ public char value;
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
+ public MutableChar(char value) {
+ this.value = value;
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableDouble.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableDouble.java
index 7294124..bd7329a 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableDouble.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.util;
-import android.os.IBinder;
+/**
+ */
+public final class MutableDouble {
+ public double value;
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
+ public MutableDouble(double value) {
+ this.value = value;
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableFloat.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableFloat.java
index 7294124..e6f2d7d 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableFloat.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.util;
-import android.os.IBinder;
+/**
+ */
+public final class MutableFloat {
+ public float value;
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
+ public MutableFloat(float value) {
+ this.value = value;
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/android/util/MutableShort.java
similarity index 74%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to core/java/android/util/MutableShort.java
index 7294124..48fb232 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/core/java/android/util/MutableShort.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.util;
-import android.os.IBinder;
+/**
+ */
+public final class MutableShort {
+ public short value;
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
+ public MutableShort(short value) {
+ this.value = value;
+ }
}
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 88b9c0d..ad160cb 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -72,11 +72,6 @@
public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
/**
- * @hide
- */
- int SHOW_AS_OVERFLOW_ALWAYS = 1 << 31;
-
- /**
* Interface definition for a callback to be invoked when a menu item is
* clicked.
*
@@ -806,12 +801,22 @@
}
/**
- * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_OVERFLOW_ALWAYS}.
- * Default value if {@code false}.
+ * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_ALWAYS}.
+ * Default value is {@code false}.
+ *
+ * @hide
+ */
+ default boolean requiresActionButton() {
+ return false;
+ }
+
+ /**
+ * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_NEVER}.
+ * Default value is {@code true}.
*
* @hide
*/
default boolean requiresOverflow() {
- return false;
+ return true;
}
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 574137b..4500862 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -84,12 +84,17 @@
/**
* Defines the duration in milliseconds a user needs to hold down the
- * appropriate button to bring up the accessibility shortcut (first time) or enable it
- * (once shortcut is configured).
+ * appropriate button to bring up the accessibility shortcut for the first time
*/
private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
/**
+ * Defines the duration in milliseconds a user needs to hold down the
+ * appropriate button to enable the accessibility shortcut once it's configured.
+ */
+ private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+
+ /**
* Defines the duration in milliseconds we will wait to see if a touch event
* is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
@@ -851,6 +856,15 @@
}
/**
+ * @return The amount of time a user needs to press the relevant keys to activate the
+ * accessibility shortcut after it's confirmed that accessibility shortcut is used.
+ * @hide
+ */
+ public long getAccessibilityShortcutKeyTimeoutAfterConfirmation() {
+ return A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION;
+ }
+
+ /**
* The amount of friction applied to scrolls and flings.
*
* @return A scalar dimensionless value representing the coefficient of
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 309366c..d665dde 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -370,7 +370,7 @@
* <p>Should only be set when the node is used for autofill purposes - it will be ignored
* when used for Assist.
*/
- public abstract void setMinTextEms(int minEms);
+ public void setMinTextEms(@SuppressWarnings("unused") int minEms) {}
/**
* Sets the maximum width in ems of the text associated with this view, when supported.
@@ -378,7 +378,7 @@
* <p>Should only be set when the node is used for autofill purposes - it will be ignored
* when used for Assist.
*/
- public abstract void setMaxTextEms(int maxEms);
+ public void setMaxTextEms(@SuppressWarnings("unused") int maxEms) {}
/**
* Sets the maximum length of the text associated with this view, when supported.
@@ -386,7 +386,7 @@
* <p>Should only be set when the node is used for autofill purposes - it will be ignored
* when used for Assist.
*/
- public abstract void setMaxTextLength(int maxLength);
+ public void setMaxTextLength(@SuppressWarnings("unused") int maxLength) {}
/**
* Call when done populating a {@link ViewStructure} returned by
diff --git a/core/java/android/view/textclassifier/Log.java b/core/java/android/view/textclassifier/Log.java
new file mode 100644
index 0000000..83ca15d
--- /dev/null
+++ b/core/java/android/view/textclassifier/Log.java
@@ -0,0 +1,46 @@
+/*
+ * 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.view.textclassifier;
+
+import android.util.Slog;
+
+/**
+ * Logging for android.view.textclassifier package.
+ */
+final class Log {
+
+ /**
+ * true: Enables full logging.
+ * false: Limits logging to debug level.
+ */
+ private static final boolean ENABLE_FULL_LOGGING = false;
+
+ private Log() {}
+
+ public static void d(String tag, String msg) {
+ Slog.d(tag, msg);
+ }
+
+ public static void e(String tag, String msg, Throwable tr) {
+ if (ENABLE_FULL_LOGGING) {
+ Slog.e(tag, msg, tr);
+ } else {
+ final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
+ Slog.d(tag, String.format("%s (%s)", msg, trString));
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index ef08747..1c07be4 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,7 +35,6 @@
import android.text.method.WordIterator;
import android.text.style.ClickableSpan;
import android.text.util.Linkify;
-import android.util.Log;
import android.util.Patterns;
import android.view.View;
import android.widget.TextViewMetrics;
@@ -163,7 +162,7 @@
}
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
- Log.e(LOG_TAG, "Error getting assist info.", t);
+ Log.e(LOG_TAG, "Error getting text classification info.", t);
}
// Getting here means something went wrong, return a NO_OP result.
return TextClassifier.NO_OP.classifyText(
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 4cb49a3..384f4f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3888,7 +3888,7 @@
if (selected == null || selected.isEmpty()) {
menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
com.android.internal.R.string.autofill)
- .setShowAsAction(MenuItem.SHOW_AS_OVERFLOW_ALWAYS);
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 199b596..631f388 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -131,7 +131,7 @@
*
* @hide
*/
- private ApplicationInfo mApplication;
+ public ApplicationInfo mApplication;
/**
* The resource ID of the layout file. (Added to the parcel)
@@ -1519,8 +1519,7 @@
@Override
public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
- return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
- && mNestedViews.mApplication.uid == parentInfo.uid;
+ return mNestedViews.hasSameAppInfo(parentInfo);
}
@Override
@@ -2138,8 +2137,7 @@
if (landscape == null || portrait == null) {
throw new RuntimeException("Both RemoteViews must be non-null");
}
- if (landscape.mApplication.uid != portrait.mApplication.uid
- || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
+ if (!landscape.hasSameAppInfo(portrait.mApplication)) {
throw new RuntimeException("Both RemoteViews must share the same package and user");
}
mApplication = portrait.mApplication;
@@ -3555,6 +3553,15 @@
}
/**
+ * Returns true if the {@link #mApplication} is same as the provided info.
+ *
+ * @hide
+ */
+ public boolean hasSameAppInfo(ApplicationInfo info) {
+ return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+ }
+
+ /**
* Parcelable.Creator that instantiates RemoteViews objects
*/
public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 0968652..e5ae0ca 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -16,11 +16,15 @@
package android.widget;
-import android.Manifest;
+import android.annotation.WorkerThread;
+import android.app.IServiceConnection;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -28,7 +32,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -38,7 +41,6 @@
import android.view.ViewGroup;
import android.widget.RemoteViews.OnClickHandler;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
import java.lang.ref.WeakReference;
@@ -48,52 +50,33 @@
import java.util.concurrent.Executor;
/**
- * An adapter to a RemoteViewsService which fetches and caches RemoteViews
- * to be later inflated as child views.
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
+ * child views.
+ *
+ * The adapter runs in the host process, typically a Launcher app.
+ *
+ * It makes a service connection to the {@link RemoteViewsService} running in the
+ * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
+ * the platform to get the bind permissions) and all interaction with the service is done on the
+ * background thread.
+ *
+ * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
+ * connection is only made when new RemoteViews are required.
+ * @hide
*/
-/** @hide */
public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
- private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
private static final String TAG = "RemoteViewsAdapter";
// The max number of items in the cache
- private static final int sDefaultCacheSize = 40;
+ private static final int DEFAULT_CACHE_SIZE = 40;
// The delay (in millis) to wait until attempting to unbind from a service after a request.
// This ensures that we don't stay continually bound to the service and that it can be destroyed
// if we need the memory elsewhere in the system.
- private static final int sUnbindServiceDelay = 5000;
+ private static final int UNBIND_SERVICE_DELAY = 5000;
// Default height for the default loading view, in case we cannot get inflate the first view
- private static final int sDefaultLoadingViewHeight = 50;
-
- // Type defs for controlling different messages across the main and worker message queues
- private static final int sDefaultMessageType = 0;
- private static final int sUnbindServiceMessageType = 1;
-
- private final Context mContext;
- private final Intent mIntent;
- private final int mAppWidgetId;
- private final Executor mAsyncViewLoadExecutor;
-
- private RemoteViewsAdapterServiceConnection mServiceConnection;
- private WeakReference<RemoteAdapterConnectionCallback> mCallback;
- private OnClickHandler mRemoteViewsOnClickHandler;
- private final FixedSizeRemoteViewsCache mCache;
- private int mVisibleWindowLowerBound;
- private int mVisibleWindowUpperBound;
-
- // A flag to determine whether we should notify data set changed after we connect
- private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
-
- // The set of requested views that are to be notified when the associated RemoteViews are
- // loaded.
- private RemoteViewsFrameLayoutRefSet mRequestedViews;
-
- private HandlerThread mWorkerThread;
- // items may be interrupted within the normally processed queues
- private Handler mWorkerQueue;
- private Handler mMainQueue;
+ private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
// We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
// structures;
@@ -110,11 +93,37 @@
// duration, the cache is dropped.
private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
+ private final Context mContext;
+ private final Intent mIntent;
+ private final int mAppWidgetId;
+ private final Executor mAsyncViewLoadExecutor;
+
+ private OnClickHandler mRemoteViewsOnClickHandler;
+ private final FixedSizeRemoteViewsCache mCache;
+ private int mVisibleWindowLowerBound;
+ private int mVisibleWindowUpperBound;
+
+ // The set of requested views that are to be notified when the associated RemoteViews are
+ // loaded.
+ private RemoteViewsFrameLayoutRefSet mRequestedViews;
+
+ private final HandlerThread mWorkerThread;
+ // items may be interrupted within the normally processed queues
+ private final Handler mMainHandler;
+ private final RemoteServiceHandler mServiceHandler;
+ private final RemoteAdapterConnectionCallback mCallback;
+
// Used to indicate to the AdapterView that it can use this Adapter immediately after
// construction (happens when we have a cached FixedSizeRemoteViewsCache).
private boolean mDataReady = false;
/**
+ * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to
+ * multiple copies of the same ApplicationInfo object.
+ */
+ private ApplicationInfo mLastRemoteViewAppInfo;
+
+ /**
* An interface for the RemoteAdapter to notify other classes when adapters
* are actually connected to/disconnected from their actual services.
*/
@@ -151,154 +160,192 @@
}
}
+ static final int MSG_REQUEST_BIND = 1;
+ static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
+ static final int MSG_LOAD_NEXT_ITEM = 3;
+ static final int MSG_UNBIND_SERVICE = 4;
+
+ private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
+ private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
+ private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
+ private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
+ private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
+
/**
- * The service connection that gets populated when the RemoteViewsService is
- * bound. This must be a static inner class to ensure that no references to the outer
- * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
- * garbage collected, and would cause us to leak activities due to the caching mechanism for
- * FrameLayouts in the adapter).
+ * Handler for various interactions with the {@link RemoteViewsService}.
*/
- private static class RemoteViewsAdapterServiceConnection extends
- IRemoteViewsAdapterConnection.Stub {
- private boolean mIsConnected;
- private boolean mIsConnecting;
- private WeakReference<RemoteViewsAdapter> mAdapter;
+ private static class RemoteServiceHandler extends Handler implements ServiceConnection {
+
+ private final WeakReference<RemoteViewsAdapter> mAdapter;
+ private final Context mContext;
+
private IRemoteViewsFactory mRemoteViewsFactory;
- public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
- mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+ // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
+ private boolean mNotifyDataSetChangedPending = false;
+ private boolean mBindRequested = false;
+
+ RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
+ super(workerLooper);
+ mAdapter = new WeakReference<>(adapter);
+ mContext = context;
}
- public synchronized void bind(Context context, int appWidgetId, Intent intent) {
- if (!mIsConnecting) {
- try {
- RemoteViewsAdapter adapter;
- final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if ((adapter = mAdapter.get()) != null) {
- mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId,
- intent, asBinder());
- } else {
- Slog.w(TAG, "bind: adapter was null");
- }
- mIsConnecting = true;
- } catch (Exception e) {
- Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
- mIsConnecting = false;
- mIsConnected = false;
- }
- }
- }
-
- public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
- try {
- RemoteViewsAdapter adapter;
- final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if ((adapter = mAdapter.get()) != null) {
- mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
- } else {
- Slog.w(TAG, "unbind: adapter was null");
- }
- mIsConnecting = false;
- } catch (Exception e) {
- Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
- mIsConnecting = false;
- mIsConnected = false;
- }
- }
-
- public synchronized void onServiceConnected(IBinder service) {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // This is called on the same thread.
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+ enqueueDeferredUnbindServiceMessage();
- // Remove any deferred unbind messages
- final RemoteViewsAdapter adapter = mAdapter.get();
- if (adapter == null) return;
+ RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter == null) {
+ return;
+ }
- // Queue up work that we need to do for the callback to run
- adapter.mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
- // Handle queued notifyDataSetChanged() if necessary
- adapter.onNotifyDataSetChanged();
- } else {
- IRemoteViewsFactory factory =
- adapter.mServiceConnection.getRemoteViewsFactory();
- try {
- if (!factory.isCreated()) {
- // We only call onDataSetChanged() if this is the factory was just
- // create in response to this bind
- factory.onDataSetChanged();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error notifying factory of data set changed in " +
- "onServiceConnected(): " + e.getMessage());
-
- // Return early to prevent anything further from being notified
- // (effectively nothing has changed)
- return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error notifying factory of data set changed in " +
- "onServiceConnected(): " + e.getMessage());
- }
-
- // Request meta data so that we have up to date data when calling back to
- // the remote adapter callback
- adapter.updateTemporaryMetaData();
-
- // Notify the host that we've connected
- adapter.mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- synchronized (adapter.mCache) {
- adapter.mCache.commitTemporaryMetaData();
- }
-
- final RemoteAdapterConnectionCallback callback =
- adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterConnected();
- }
- }
- });
- }
-
- // Enqueue unbind message
- adapter.enqueueDeferredUnbindServiceMessage();
- mIsConnected = true;
- mIsConnecting = false;
+ if (mNotifyDataSetChangedPending) {
+ mNotifyDataSetChangedPending = false;
+ Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
+ handleMessage(msg);
+ msg.recycle();
+ } else {
+ if (!sendNotifyDataSetChange(false)) {
+ return;
}
- });
+
+ // Request meta data so that we have up to date data when calling back to
+ // the remote adapter callback
+ adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
+ }
}
- public synchronized void onServiceDisconnected() {
- mIsConnected = false;
- mIsConnecting = false;
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
mRemoteViewsFactory = null;
+ RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter != null) {
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
+ }
+ }
- // Clear the main/worker queues
- final RemoteViewsAdapter adapter = mAdapter.get();
- if (adapter == null) return;
+ @Override
+ public void handleMessage(Message msg) {
+ RemoteViewsAdapter adapter = mAdapter.get();
- adapter.mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- // Dequeue any unbind messages
- adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
-
- final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterDisconnected();
+ switch (msg.what) {
+ case MSG_REQUEST_BIND: {
+ if (adapter == null || mRemoteViewsFactory != null) {
+ enqueueDeferredUnbindServiceMessage();
}
+ if (mBindRequested) {
+ return;
+ }
+ int flags = Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+ final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
+ Intent intent = (Intent) msg.obj;
+ int appWidgetId = msg.arg1;
+ mBindRequested = AppWidgetManager.getInstance(mContext)
+ .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
+ return;
}
- });
+ case MSG_NOTIFY_DATA_SET_CHANGED: {
+ enqueueDeferredUnbindServiceMessage();
+ if (adapter == null) {
+ return;
+ }
+ if (mRemoteViewsFactory == null) {
+ mNotifyDataSetChangedPending = true;
+ adapter.requestBindService();
+ return;
+ }
+ if (!sendNotifyDataSetChange(true)) {
+ return;
+ }
+
+ // Flush the cache so that we can reload new items from the service
+ synchronized (adapter.mCache) {
+ adapter.mCache.reset();
+ }
+
+ // Re-request the new metadata (only after the notification to the factory)
+ adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+ int newCount;
+ int[] visibleWindow;
+ synchronized (adapter.mCache.getTemporaryMetaData()) {
+ newCount = adapter.mCache.getTemporaryMetaData().count;
+ visibleWindow = adapter.getVisibleWindow(newCount);
+ }
+
+ // Pre-load (our best guess of) the views which are currently visible in the
+ // AdapterView. This mitigates flashing and flickering of loading views when a
+ // widget notifies that its data has changed.
+ for (int position : visibleWindow) {
+ // Because temporary meta data is only ever modified from this thread
+ // (ie. mWorkerThread), it is safe to assume that count is a valid
+ // representation.
+ if (position < newCount) {
+ adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
+ }
+ }
+
+ // Propagate the notification back to the base adapter
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+ adapter.mMainHandler.sendEmptyMessage(
+ MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
+ return;
+ }
+
+ case MSG_LOAD_NEXT_ITEM: {
+ if (adapter == null || mRemoteViewsFactory == null) {
+ return;
+ }
+ removeMessages(MSG_UNBIND_SERVICE);
+ // Get the next index to load
+ final int position = adapter.mCache.getNextIndexToLoad();
+ if (position > -1) {
+ // Load the item, and notify any existing RemoteViewsFrameLayouts
+ adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
+
+ // Queue up for the next one to load
+ sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+ } else {
+ // No more items to load, so queue unbind
+ enqueueDeferredUnbindServiceMessage();
+ }
+ return;
+ }
+ case MSG_UNBIND_SERVICE: {
+ unbindNow();
+ return;
+ }
+ }
}
- public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
- return mRemoteViewsFactory;
+ protected void unbindNow() {
+ if (mBindRequested) {
+ mBindRequested = false;
+ mContext.unbindService(this);
+ }
+ mRemoteViewsFactory = null;
}
- public synchronized boolean isConnected() {
- return mIsConnected;
+ private boolean sendNotifyDataSetChange(boolean always) {
+ try {
+ if (always || !mRemoteViewsFactory.isCreated()) {
+ mRemoteViewsFactory.onDataSetChanged();
+ }
+ return true;
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+ return false;
+ }
+ }
+
+ private void enqueueDeferredUnbindServiceMessage() {
+ removeMessages(MSG_UNBIND_SERVICE);
+ sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
}
}
@@ -309,6 +356,8 @@
static class RemoteViewsFrameLayout extends AppWidgetHostView {
private final FixedSizeRemoteViewsCache mCache;
+ public int cacheIndex = -1;
+
public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
super(context);
mCache = cache;
@@ -359,26 +408,23 @@
* Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
* adapter that have not yet had their RemoteViews loaded.
*/
- private class RemoteViewsFrameLayoutRefSet {
- private final SparseArray<LinkedList<RemoteViewsFrameLayout>> mReferences =
- new SparseArray<>();
- private final HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
- mViewToLinkedList = new HashMap<>();
+ private class RemoteViewsFrameLayoutRefSet
+ extends SparseArray<LinkedList<RemoteViewsFrameLayout>> {
/**
* Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
*/
public void add(int position, RemoteViewsFrameLayout layout) {
- LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+ LinkedList<RemoteViewsFrameLayout> refs = get(position);
// Create the list if necessary
if (refs == null) {
- refs = new LinkedList<RemoteViewsFrameLayout>();
- mReferences.put(position, refs);
+ refs = new LinkedList<>();
+ put(position, refs);
}
- mViewToLinkedList.put(layout, refs);
// Add the references to the list
+ layout.cacheIndex = position;
refs.add(layout);
}
@@ -389,18 +435,13 @@
public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
if (view == null) return;
- final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+ // Remove this set from the original mapping
+ final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
if (refs != null) {
// Notify all the references for that position of the newly loaded RemoteViews
for (final RemoteViewsFrameLayout ref : refs) {
ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
- if (mViewToLinkedList.containsKey(ref)) {
- mViewToLinkedList.remove(ref);
- }
}
- refs.clear();
- // Remove this set from the original mapping
- mReferences.remove(position);
}
}
@@ -408,20 +449,14 @@
* We need to remove views from this set if they have been recycled by the AdapterView.
*/
public void removeView(RemoteViewsFrameLayout rvfl) {
- if (mViewToLinkedList.containsKey(rvfl)) {
- mViewToLinkedList.get(rvfl).remove(rvfl);
- mViewToLinkedList.remove(rvfl);
+ if (rvfl.cacheIndex < 0) {
+ return;
}
- }
-
- /**
- * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
- */
- public void clear() {
- // We currently just clear the references, and leave all the previous layouts returned
- // in their default state of the loading view.
- mReferences.clear();
- mViewToLinkedList.clear();
+ final LinkedList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
+ if (refs != null) {
+ refs.remove(rvfl);
+ }
+ rvfl.cacheIndex = -1;
}
}
@@ -512,7 +547,6 @@
*
*/
private static class FixedSizeRemoteViewsCache {
- private static final String TAG = "FixedSizeRemoteViewsCache";
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
// The meta data objects are made final so that they can be locked on independently
@@ -534,7 +568,7 @@
// too much memory.
private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
- // An array of indices to load, Indices which are explicitely requested are set to true,
+ // An array of indices to load, Indices which are explicitly requested are set to true,
// and those determined by the preloading algorithm to prefetch are set to false.
private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
@@ -676,7 +710,7 @@
}
}
- int count = 0;
+ int count;
synchronized (mMetaData) {
count = mMetaData.count;
}
@@ -791,9 +825,11 @@
// Initialize the worker thread
mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
mWorkerThread.start();
- mWorkerQueue = new Handler(mWorkerThread.getLooper());
- mMainQueue = new Handler(Looper.myLooper(), this);
+ mMainHandler = new Handler(Looper.myLooper(), this);
+ mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
+ context.getApplicationContext());
mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
+ mCallback = callback;
if (sCacheRemovalThread == null) {
sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -801,10 +837,6 @@
sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
}
- // Initialize the cache and the service connection on startup
- mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
- mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
-
RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
mAppWidgetId);
@@ -819,7 +851,7 @@
}
}
} else {
- mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+ mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE);
}
if (!mDataReady) {
requestBindService();
@@ -830,9 +862,8 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mWorkerThread != null) {
- mWorkerThread.quit();
- }
+ mServiceHandler.unbindNow();
+ mWorkerThread.quit();
} finally {
super.finalize();
}
@@ -869,16 +900,13 @@
sCachedRemoteViewsCaches.put(key, mCache);
}
- Runnable r = new Runnable() {
- @Override
- public void run() {
- synchronized (sCachedRemoteViewsCaches) {
- if (sCachedRemoteViewsCaches.containsKey(key)) {
- sCachedRemoteViewsCaches.remove(key);
- }
- if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
- sRemoteViewsCacheRemoveRunnables.remove(key);
- }
+ Runnable r = () -> {
+ synchronized (sCachedRemoteViewsCaches) {
+ if (sCachedRemoteViewsCaches.containsKey(key)) {
+ sCachedRemoteViewsCaches.remove(key);
+ }
+ if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+ sRemoteViewsCacheRemoveRunnables.remove(key);
}
}
};
@@ -887,54 +915,8 @@
}
}
- private void loadNextIndexInBackground() {
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- if (mServiceConnection.isConnected()) {
- // Get the next index to load
- int position = -1;
- synchronized (mCache) {
- position = mCache.getNextIndexToLoad();
- }
- if (position > -1) {
- // Load the item, and notify any existing RemoteViewsFrameLayouts
- updateRemoteViews(position, true);
-
- // Queue up for the next one to load
- loadNextIndexInBackground();
- } else {
- // No more items to load, so queue unbind
- enqueueDeferredUnbindServiceMessage();
- }
- }
- }
- });
- }
-
- private void processException(String method, Exception e) {
- Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
-
- // If we encounter a crash when updating, we should reset the metadata & cache and trigger
- // a notifyDataSetChanged to update the widget accordingly
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- metaData.reset();
- }
- synchronized (mCache) {
- mCache.reset();
- }
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- superNotifyDataSetChanged();
- }
- });
- }
-
- private void updateTemporaryMetaData() {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+ @WorkerThread
+ private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
try {
// get the properties/first view (so that we can use it to
// measure our dummy views)
@@ -958,40 +940,54 @@
tmpMetaData.count = count;
tmpMetaData.loadingTemplate = loadingTemplate;
}
- } catch(RemoteException e) {
- processException("updateMetaData", e);
- } catch(RuntimeException e) {
- processException("updateMetaData", e);
+ } catch (RemoteException | RuntimeException e) {
+ Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
+
+ // If we encounter a crash when updating, we should reset the metadata & cache
+ // and trigger a notifyDataSetChanged to update the widget accordingly
+ synchronized (mCache.getMetaData()) {
+ mCache.getMetaData().reset();
+ }
+ synchronized (mCache) {
+ mCache.reset();
+ }
+ mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
}
}
- private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+ @WorkerThread
+ private void updateRemoteViews(IRemoteViewsFactory factory, int position,
+ boolean notifyWhenLoaded) {
// Load the item information from the remote service
- RemoteViews remoteViews = null;
- long itemId = 0;
+ final RemoteViews remoteViews;
+ final long itemId;
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
- } catch (RemoteException e) {
+
+ if (remoteViews == null) {
+ throw new RuntimeException("Null remoteViews");
+ }
+ } catch (RemoteException | RuntimeException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
- return;
}
- if (remoteViews == null) {
- // If a null view was returned, we break early to prevent it from getting
- // into our cache and causing problems later. The effect is that the child at this
- // position will remain as a loading view until it is updated.
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
- "returned from RemoteViewsFactory.");
- return;
+ if (remoteViews.mApplication != null) {
+ // We keep track of last application info. This helps when all the remoteViews have
+ // same applicationInfo, which should be the case for a typical adapter. But if every
+ // view has different application info, there will not be any optimization.
+ if (mLastRemoteViewAppInfo != null
+ && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
+ // We should probably also update the remoteViews for nested ViewActions.
+ // Hopefully, RemoteViews in an adapter would be less complicated.
+ remoteViews.mApplication = mLastRemoteViewAppInfo;
+ } else {
+ mLastRemoteViewAppInfo = remoteViews.mApplication;
+ }
}
int layoutId = remoteViews.getLayoutId();
@@ -1004,21 +1000,15 @@
}
synchronized (mCache) {
if (viewTypeInRange) {
- int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
- mVisibleWindowUpperBound, cacheCount);
+ int[] visibleWindow = getVisibleWindow(cacheCount);
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId, visibleWindow);
- // Notify all the views that we have previously returned for this index that
- // there is new data for it.
- final RemoteViews rv = remoteViews;
if (notifyWhenLoaded) {
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
- }
- });
+ // Notify all the views that we have previously returned for this index that
+ // there is new data for it.
+ Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
+ remoteViews).sendToTarget();
}
} else {
// We need to log an error here, as the the view type count specified by the
@@ -1057,7 +1047,7 @@
}
public int getItemViewType(int position) {
- int typeId = 0;
+ final int typeId;
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
typeId = mCache.getMetaDataAt(position).typeId;
@@ -1088,14 +1078,13 @@
synchronized (mCache) {
RemoteViews rv = mCache.getRemoteViewsAt(position);
boolean isInCache = (rv != null);
- boolean isConnected = mServiceConnection.isConnected();
boolean hasNewItems = false;
if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
}
- if (!isInCache && !isConnected) {
+ if (!isInCache) {
// Requesting bind service will trigger a super.notifyDataSetChanged(), which will
// in turn trigger another request to getView()
requestBindService();
@@ -1115,7 +1104,9 @@
if (isInCache) {
// Apply the view synchronously if possible, to avoid flickering
layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
- if (hasNewItems) loadNextIndexInBackground();
+ if (hasNewItems) {
+ mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+ }
} else {
// If the views is not loaded, apply the loading view. If the loading view doesn't
// exist, the layout will create a default view based on the firstView height.
@@ -1125,7 +1116,7 @@
false);
mRequestedViews.add(position, layout);
mCache.queueRequestedPositionToLoad(position);
- loadNextIndexInBackground();
+ mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
}
return layout;
}
@@ -1149,69 +1140,12 @@
return getCount() <= 0;
}
- private void onNotifyDataSetChanged() {
- // Complete the actual notifyDataSetChanged() call initiated earlier
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
- try {
- factory.onDataSetChanged();
- } catch (RemoteException e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
- // Return early to prevent from further being notified (since nothing has
- // changed)
- return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
- return;
- }
-
- // Flush the cache so that we can reload new items from the service
- synchronized (mCache) {
- mCache.reset();
- }
-
- // Re-request the new metadata (only after the notification to the factory)
- updateTemporaryMetaData();
- int newCount;
- int[] visibleWindow;
- synchronized(mCache.getTemporaryMetaData()) {
- newCount = mCache.getTemporaryMetaData().count;
- visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
- mVisibleWindowUpperBound, newCount);
- }
-
- // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
- // This mitigates flashing and flickering of loading views when a widget notifies that
- // its data has changed.
- for (int i: visibleWindow) {
- // Because temporary meta data is only ever modified from this thread (ie.
- // mWorkerThread), it is safe to assume that count is a valid representation.
- if (i < newCount) {
- updateRemoteViews(i, false);
- }
- }
-
- // Propagate the notification back to the base adapter
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mCache) {
- mCache.commitTemporaryMetaData();
- }
-
- superNotifyDataSetChanged();
- enqueueDeferredUnbindServiceMessage();
- }
- });
-
- // Reset the notify flagflag
- mNotifyDataSetChangedAfterOnServiceConnected = false;
- }
-
/**
* Returns a sorted array of all integers between lower and upper.
*/
- private int[] getVisibleWindow(int lower, int upper, int count) {
+ private int[] getVisibleWindow(int count) {
+ int lower = mVisibleWindowLowerBound;
+ int upper = mVisibleWindowUpperBound;
// In the case that the window is invalid or uninitialized, return an empty window.
if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
return new int[0];
@@ -1241,23 +1175,8 @@
}
public void notifyDataSetChanged() {
- // Dequeue any unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
-
- // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
- // connect
- if (!mServiceConnection.isConnected()) {
- mNotifyDataSetChangedAfterOnServiceConnected = true;
- requestBindService();
- return;
- }
-
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- onNotifyDataSetChanged();
- }
- });
+ mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+ mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
}
void superNotifyDataSetChanged() {
@@ -1266,35 +1185,38 @@
@Override
public boolean handleMessage(Message msg) {
- boolean result = false;
switch (msg.what) {
- case sUnbindServiceMessageType:
- if (mServiceConnection.isConnected()) {
- mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
+ case MSG_MAIN_HANDLER_COMMIT_METADATA: {
+ mCache.commitTemporaryMetaData();
+ return true;
}
- result = true;
- break;
- default:
- break;
+ case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
+ superNotifyDataSetChanged();
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
+ if (mCallback != null) {
+ mCallback.onRemoteAdapterConnected();
+ }
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
+ if (mCallback != null) {
+ mCallback.onRemoteAdapterDisconnected();
+ }
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
+ mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
+ return true;
+ }
}
- return result;
+ return false;
}
- private void enqueueDeferredUnbindServiceMessage() {
- // Remove any existing deferred-unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
- mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
- }
-
- private boolean requestBindService() {
- // Try binding the service (which will start it if it's not already running)
- if (!mServiceConnection.isConnected()) {
- mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
- }
-
- // Remove any existing deferred-unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
- return mServiceConnection.isConnected();
+ private void requestBindService() {
+ mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+ Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
}
private static class HandlerThreadExecutor implements Executor {
@@ -1322,7 +1244,7 @@
remoteViews = views;
float density = context.getResources().getDisplayMetrics().density;
- defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+ defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
}
public void loadFirstViewHeight(
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3d5cd0f..d0719ee 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -238,7 +238,7 @@
mSearchView.setOnQueryTextListener(this);
// Restore previous search status
- if (mPreviousSearch != null) {
+ if (!TextUtils.isEmpty(mPreviousSearch)) {
searchMenuItem.expandActionView();
mSearchView.setIconified(false);
mSearchView.setActivated(true);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index caf35b3..a4da6b9c 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -26,6 +26,8 @@
import android.os.Bundle;
import android.os.IBinder;
import android.widget.RemoteViews;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
/** {@hide} */
interface IAppWidgetService {
@@ -62,9 +64,9 @@
void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission);
boolean bindAppWidgetId(in String callingPackage, int appWidgetId,
int providerProfileId, in ComponentName providerComponent, in Bundle options);
- void bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
- in IBinder connection);
- void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent);
+ boolean bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
+ IApplicationThread caller, IBinder token, IServiceConnection connection, int flags);
+
int[] getAppWidgetIds(in ComponentName providerComponent);
boolean isBoundWidgetPackage(String packageName, int userId);
boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5c310b1..239d709 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6428,6 +6428,11 @@
}
@Override
+ public Timer getWifiScanTimer() {
+ return mWifiScanTimer;
+ }
+
+ @Override
public int getWifiScanBackgroundCount(int which) {
if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
return 0;
@@ -6454,6 +6459,14 @@
}
@Override
+ public Timer getWifiScanBackgroundTimer() {
+ if (mWifiScanTimer == null) {
+ return null;
+ }
+ return mWifiScanTimer.getSubTimer();
+ }
+
+ @Override
public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
if (mWifiBatchedScanTimer[csphBin] == null) {
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
index 46098c5..70b6f96 100644
--- a/core/java/com/android/internal/util/WakeupMessage.java
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -47,17 +47,19 @@
protected final int mCmd, mArg1, mArg2;
@VisibleForTesting
protected final Object mObj;
+ private final Runnable mRunnable;
private boolean mScheduled;
public WakeupMessage(Context context, Handler handler,
String cmdName, int cmd, int arg1, int arg2, Object obj) {
- mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mAlarmManager = getAlarmManager(context);
mHandler = handler;
mCmdName = cmdName;
mCmd = cmd;
mArg1 = arg1;
mArg2 = arg2;
mObj = obj;
+ mRunnable = null;
}
public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
@@ -73,6 +75,21 @@
this(context, handler, cmdName, cmd, 0, 0, null);
}
+ public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = 0;
+ mArg1 = 0;
+ mArg2 = 0;
+ mObj = null;
+ mRunnable = runnable;
+ }
+
+ private static AlarmManager getAlarmManager(Context context) {
+ return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
/**
* Schedule the message to be delivered at the time in milliseconds of the
* {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
@@ -107,7 +124,12 @@
mScheduled = false;
}
if (stillScheduled) {
- Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ Message msg;
+ if (mRunnable == null) {
+ msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ } else {
+ msg = Message.obtain(mHandler, mRunnable);
+ }
mHandler.dispatchMessage(msg);
msg.recycle();
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 15e271e..9d012de 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -658,7 +658,7 @@
@Override
public boolean requiresOverflow() {
- return (mShowAsAction & SHOW_AS_OVERFLOW_ALWAYS) == SHOW_AS_OVERFLOW_ALWAYS;
+ return !requiresActionButton() && !requestsActionButton();
}
public void setIsActionButton(boolean isActionButton) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index f63b5a2..e3b1c01 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -64,6 +64,7 @@
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -118,6 +119,36 @@
};
/**
+ * Sorts the list of menu items to conform to certain requirements.
+ */
+ private final Comparator<MenuItem> mMenuItemComparator = (menuItem1, menuItem2) -> {
+ // Ensure the assist menu item is always the first item:
+ if (menuItem1.getItemId() == android.R.id.textAssist) {
+ return menuItem2.getItemId() == android.R.id.textAssist ? 0 : -1;
+ }
+ if (menuItem2.getItemId() == android.R.id.textAssist) {
+ return 1;
+ }
+
+ // Order by SHOW_AS_ACTION type:
+ if (menuItem1.requiresActionButton()) {
+ return menuItem2.requiresActionButton() ? 0 : -1;
+ }
+ if (menuItem2.requiresActionButton()) {
+ return 1;
+ }
+ if (menuItem1.requiresOverflow()) {
+ return menuItem2.requiresOverflow() ? 0 : 1;
+ }
+ if (menuItem2.requiresOverflow()) {
+ return -1;
+ }
+
+ // Order by order value:
+ return menuItem1.getOrder() - menuItem2.getOrder();
+ };
+
+ /**
* Initializes a floating toolbar.
*/
public FloatingToolbar(Window window) {
@@ -230,7 +261,7 @@
private void doShow() {
List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
- tidy(menuItems);
+ menuItems.sort(mMenuItemComparator);
if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
mPopup.dismiss();
mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
@@ -288,36 +319,6 @@
return menuItems;
}
- /**
- * Update the list of menu items to conform to certain requirements.
- */
- private void tidy(List<MenuItem> menuItems) {
- int assistItemIndex = -1;
- Drawable assistItemDrawable = null;
-
- final int size = menuItems.size();
- for (int i = 0; i < size; i++) {
- final MenuItem menuItem = menuItems.get(i);
-
- if (menuItem.getItemId() == android.R.id.textAssist) {
- assistItemIndex = i;
- assistItemDrawable = menuItem.getIcon();
- }
-
- // Remove icons for all menu items with text.
- if (!TextUtils.isEmpty(menuItem.getTitle())) {
- menuItem.setIcon(null);
- }
- }
- if (assistItemIndex > -1) {
- final MenuItem assistMenuItem = menuItems.remove(assistItemIndex);
- // Ensure the assist menu item preserves its icon.
- assistMenuItem.setIcon(assistItemDrawable);
- // Ensure the assist menu item is always the first item.
- menuItems.add(0, assistMenuItem);
- }
- }
-
private void registerOrientationHandler() {
unregisterOrientationHandler();
mWindow.getDecorView().addOnLayoutChangeListener(mOrientationChangeHandler);
@@ -1148,7 +1149,8 @@
// add the overflow menu items to the end of the remainingMenuItems list.
final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
for (MenuItem menuItem : menuItems) {
- if (menuItem.requiresOverflow()) {
+ if (menuItem.getItemId() != android.R.id.textAssist
+ && menuItem.requiresOverflow()) {
overflowMenuItems.add(menuItem);
} else {
remainingMenuItems.add(menuItem);
@@ -1171,7 +1173,9 @@
break;
}
- View menuItemButton = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+ final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+ final View menuItemButton = createMenuItemButton(
+ mContext, menuItem, mIconTextSpacing, showIcon);
// Adding additional start padding for the first button to even out button spacing.
if (isFirstItem) {
@@ -1193,16 +1197,17 @@
}
menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- final int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+ final int menuItemButtonWidth = Math.min(
+ menuItemButton.getMeasuredWidth(), toolbarWidth);
final boolean isNewGroup = !isFirstItem && lastGroupId != menuItem.getGroupId();
final int extraPadding = isNewGroup ? menuItemButton.getPaddingEnd() * 2 : 0;
// Check if we can fit an item while reserving space for the overflowButton.
- boolean canFitWithOverflow =
+ final boolean canFitWithOverflow =
menuItemButtonWidth <=
availableWidth - mOverflowButtonSize.getWidth() - extraPadding;
- boolean canFitNoOverflow =
+ final boolean canFitNoOverflow =
isLastItem && menuItemButtonWidth <= availableWidth - extraPadding;
if (canFitWithOverflow || canFitNoOverflow) {
if (isNewGroup) {
@@ -1211,7 +1216,8 @@
// Add extra padding to the end of the previous button.
// Half of the extra padding (less borderWidth) goes to the previous button.
- View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1);
+ final View previousButton = mMainPanel.getChildAt(
+ mMainPanel.getChildCount() - 1);
final int prevPaddingEnd = previousButton.getPaddingEnd()
+ extraPadding / 2 - dividerWidth;
previousButton.setPaddingRelative(
@@ -1612,7 +1618,8 @@
public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
Preconditions.checkNotNull(menuItem);
if (convertView != null) {
- updateMenuItemButton(convertView, menuItem, mIconTextSpacing);
+ updateMenuItemButton(
+ convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
} else {
convertView = createMenuButton(menuItem);
}
@@ -1621,17 +1628,26 @@
}
public int calculateWidth(MenuItem menuItem) {
- updateMenuItemButton(mCalculator, menuItem, mIconTextSpacing);
+ updateMenuItemButton(
+ mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
mCalculator.measure(
View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
return mCalculator.getMeasuredWidth();
}
private View createMenuButton(MenuItem menuItem) {
- View button = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+ View button = createMenuItemButton(
+ mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
button.setPadding(mSidePadding, 0, mSidePadding, 0);
return button;
}
+
+ private boolean shouldShowIcon(MenuItem menuItem) {
+ if (menuItem != null) {
+ return menuItem.getGroupId() == android.R.id.textAssist;
+ }
+ return false;
+ }
}
}
@@ -1639,11 +1655,11 @@
* Creates and returns a menu button for the specified menu item.
*/
private static View createMenuItemButton(
- Context context, MenuItem menuItem, int iconTextSpacing) {
+ Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
final View menuItemButton = LayoutInflater.from(context)
.inflate(R.layout.floating_popup_menu_button, null);
if (menuItem != null) {
- updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing);
+ updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
}
return menuItemButton;
}
@@ -1652,18 +1668,19 @@
* Updates the specified menu item button with the specified menu item data.
*/
private static void updateMenuItemButton(
- View menuItemButton, MenuItem menuItem, int iconTextSpacing) {
- final TextView buttonText = (TextView) menuItemButton.findViewById(
+ View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+ final TextView buttonText = menuItemButton.findViewById(
R.id.floating_toolbar_menu_item_text);
+ buttonText.setEllipsize(null);
if (TextUtils.isEmpty(menuItem.getTitle())) {
buttonText.setVisibility(View.GONE);
} else {
buttonText.setVisibility(View.VISIBLE);
buttonText.setText(menuItem.getTitle());
}
- final ImageView buttonIcon = (ImageView) menuItemButton
- .findViewById(R.id.floating_toolbar_menu_item_image);
- if (menuItem.getIcon() == null) {
+ final ImageView buttonIcon = menuItemButton.findViewById(
+ R.id.floating_toolbar_menu_item_image);
+ if (menuItem.getIcon() == null || !showIcon) {
buttonIcon.setVisibility(View.GONE);
if (buttonText != null) {
buttonText.setPaddingRelative(0, 0, 0, 0);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 928626b..4fbd265 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -87,7 +87,6 @@
"android_view_ThreadedRenderer.cpp",
"android_view_VelocityTracker.cpp",
"android_text_AndroidCharacter.cpp",
- "android_text_AndroidBidi.cpp",
"android_text_Hyphenator.cpp",
"android_text_StaticLayout.cpp",
"android_os_Debug.cpp",
@@ -302,13 +301,4 @@
// GraphicsJNI.h includes hwui headers
"libhwui",
],
-
- product_variables: {
- debuggable: {
- cflags: ["-D__ANDROID_DEBUGGABLE__"]
- },
- treble: {
- cflags: ["-D__ANDROID_TREBLE__"]
- },
- },
}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index da6d5aa..8977891 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,7 +177,6 @@
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_text_StaticLayout(JNIEnv *env);
-extern int register_android_text_AndroidBidi(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
@@ -1335,7 +1334,6 @@
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_Hyphenator),
REG_JNI(register_android_text_StaticLayout),
- REG_JNI(register_android_text_AndroidBidi),
REG_JNI(register_android_view_InputDevice),
REG_JNI(register_android_view_KeyCharacterMap),
REG_JNI(register_android_os_Process),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e33d6ea..d3da21b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -755,7 +755,7 @@
return;
}
- int fd = open("/proc/meminfo", O_RDONLY);
+ int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ALOGW("Unable to open /proc/meminfo: %s\n", strerror(errno));
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 59ca050..fe14d48 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -306,7 +306,7 @@
jstring serviceNameObj) {
using ::android::hidl::base::V1_0::IBase;
- using ::android::hidl::manager::V1_0::IServiceManager;
+ using ::android::hardware::details::getRawServiceInternal;
if (ifaceNameObj == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
@@ -317,22 +317,12 @@
return NULL;
}
- auto manager = hardware::defaultServiceManager();
-
- if (manager == nullptr) {
- LOG(ERROR) << "Could not get hwservicemanager.";
- signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
- return NULL;
- }
-
const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);
if (ifaceNameCStr == NULL) {
return NULL; // XXX exception already pending?
}
std::string ifaceName(ifaceNameCStr);
env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);
- ::android::hardware::hidl_string ifaceNameHStr;
- ifaceNameHStr.setToExternal(ifaceName.c_str(), ifaceName.size());
const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);
if (serviceNameCStr == NULL) {
@@ -340,50 +330,9 @@
}
std::string serviceName(serviceNameCStr);
env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
- ::android::hardware::hidl_string serviceNameHStr;
- serviceNameHStr.setToExternal(serviceName.c_str(), serviceName.size());
- LOG(INFO) << "Looking for service "
- << ifaceName
- << "/"
- << serviceName;
-
- Return<IServiceManager::Transport> transportRet =
- manager->getTransport(ifaceNameHStr, serviceNameHStr);
-
- if (!transportRet.isOk()) {
- signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
- return NULL;
- }
-
- IServiceManager::Transport transport = transportRet;
-
-#ifdef __ANDROID_TREBLE__
-#ifdef __ANDROID_DEBUGGABLE__
- const char* testingOverride = std::getenv("TREBLE_TESTING_OVERRIDE");
- const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY)
- && testingOverride && !strcmp(testingOverride, "true");
-#else // __ANDROID_TREBLE__ but not __ANDROID_DEBUGGABLE__
- const bool vintfLegacy = false;
-#endif // __ANDROID_DEBUGGABLE__
-#else // not __ANDROID_TREBLE__
- const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY);
-#endif // __ANDROID_TREBLE__";
-
- if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
- LOG(ERROR) << "service " << ifaceName << " declares transport method "
- << toString(transport) << " but framework expects hwbinder.";
- signalExceptionForError(env, NAME_NOT_FOUND, true /* canThrowRemoteException */);
- return NULL;
- }
-
- Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceNameHStr, serviceNameHStr);
-
- if (!ret.isOk()) {
- signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
- return NULL;
- }
-
+ // TODO(b/67981006): true /* retry */
+ sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */);
sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
if (service == NULL) {
diff --git a/core/jni/android_text_AndroidBidi.cpp b/core/jni/android_text_AndroidBidi.cpp
deleted file mode 100644
index f72f0f0..0000000
--- a/core/jni/android_text_AndroidBidi.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/* //device/libs/android_runtime/android_text_AndroidBidi.cpp
-**
-** Copyright 2010, 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 "AndroidUnicode"
-
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include "utils/misc.h"
-#include "utils/Log.h"
-#include "unicode/ubidi.h"
-#include <minikin/Emoji.h>
-
-namespace android {
-
-static jint runBidi(JNIEnv* env, jobject obj, jint dir, jcharArray chsArray,
- jbyteArray infoArray, jint n, jboolean haveInfo)
-{
- // Parameters are checked on java side
- // Failures from GetXXXArrayElements indicate a serious out-of-memory condition
- // that we don't bother to report, we're probably dead anyway.
- jint result = 0;
- jchar* chs = env->GetCharArrayElements(chsArray, NULL);
- if (chs != NULL) {
- jbyte* info = env->GetByteArrayElements(infoArray, NULL);
- if (info != NULL) {
- UErrorCode status = U_ZERO_ERROR;
- UBiDi* bidi = ubidi_openSized(n, 0, &status);
- // Set callbacks to override bidi classes of new emoji
- ubidi_setClassCallback(
- bidi, minikin::emojiBidiOverride, nullptr, nullptr, nullptr, &status);
- ubidi_setPara(bidi, reinterpret_cast<const UChar*>(chs), n, dir, NULL, &status);
- if (U_SUCCESS(status)) {
- for (int i = 0; i < n; ++i) {
- info[i] = ubidi_getLevelAt(bidi, i);
- }
- result = ubidi_getParaLevel(bidi);
- } else {
- jniThrowException(env, "java/lang/RuntimeException", NULL);
- }
- ubidi_close(bidi);
-
- env->ReleaseByteArrayElements(infoArray, info, 0);
- }
- env->ReleaseCharArrayElements(chsArray, chs, JNI_ABORT);
- }
- return result;
-}
-
-static const JNINativeMethod gMethods[] = {
- { "runBidi", "(I[C[BIZ)I", (void*) runBidi }
-};
-
-int register_android_text_AndroidBidi(JNIEnv* env)
-{
- return RegisterMethodsOrDie(env, "android/text/AndroidBidi", gMethods, NELEM(gMethods));
-}
-
-}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 33c8304..dec6c02 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -219,7 +219,7 @@
strcpy(cmdline, "unknown");
sprintf(proc_path, "/proc/%d/cmdline", pid);
- fd = open(proc_path, O_RDONLY);
+ fd = open(proc_path, O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
int rc = read(fd, cmdline, sizeof(cmdline)-1);
cmdline[rc] = 0;
@@ -555,7 +555,7 @@
return false;
}
- int fd = open(text, O_WRONLY);
+ int fd = open(text, O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
sprintf(text, "%" PRId32, pid);
write(fd, text, strlen(text));
@@ -603,7 +603,7 @@
static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num)
{
- int fd = open("/proc/meminfo", O_RDONLY);
+ int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ALOGW("Unable to open /proc/meminfo");
@@ -716,7 +716,7 @@
sizesArray[i] = 0;
}
- int fd = open(file.string(), O_RDONLY);
+ int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
const size_t BUFFER_SIZE = 2048;
@@ -1023,7 +1023,7 @@
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return JNI_FALSE;
}
- int fd = open(file8, O_RDONLY);
+ int fd = open(file8, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (kDebugProc) {
@@ -1157,7 +1157,7 @@
char data[PATH_MAX];
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
- int fd = open(path, O_RDONLY);
+ int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
continue;
}
diff --git a/core/proto/android/app/jobparameters.proto b/core/proto/android/app/jobparameters.proto
new file mode 100644
index 0000000..4f6a2a2
--- /dev/null
+++ b/core/proto/android/app/jobparameters.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.JobParameters object.
+ */
+message JobParametersProto {
+ enum CancelReason {
+ REASON_CANCELLED = 0;
+ REASON_CONSTRAINTS_NOT_SATISFIED = 1;
+ REASON_PREEMPT = 2;
+ REASON_TIMEOUT = 3;
+ REASON_DEVICE_IDLE = 4;
+ }
+}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 38879c0..c33c0a0 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -19,6 +19,8 @@
package android.os;
+import "frameworks/base/core/proto/android/app/jobparameters.proto";
+import "frameworks/base/core/proto/android/os/powermanager.proto";
import "frameworks/base/core/proto/android/telephony/signalstrength.proto";
message BatteryStatsProto {
@@ -356,11 +358,12 @@
optional PowerUseSummary power_use_summary = 18;
message ResourcePowerManager {
+ // Either StateName or StateName.VoterName.
optional string name = 1;
optional TimerProto total = 2;
optional TimerProto screen_off = 3;
}
- optional ResourcePowerManager resource_power_manager = 19;
+ repeated ResourcePowerManager resource_power_manager = 19;
message ScreenBrightness {
enum Name {
@@ -436,12 +439,332 @@
}
message TimerProto {
+ // This may be an apportioned time.
optional int64 duration_ms = 1;
optional int64 count = 2;
+ // The max duration if it is being tracked. Not all Timer subclasses
+ // track the max duration.
+ optional int64 max_duration_ms = 3;
+ // The current time the timer has been active, if it is being tracked.
+ // Not all Timer subclasses track the current duration.
+ optional int64 current_duration_ms = 4;
+ // The total cumulative duration (i.e. sum of past durations) that this timer
+ // has been on since reset. This may differ from duration_ms since, depending
+ // on the Timer, getTotalTimeLocked may represent the total 'blamed' or
+ // 'pooled' time, rather than the actual time. By contrast, total_duration_ms
+ // always gives the actual total time. Not all Timer subclasses track the
+ // total duration.
+ optional int64 total_duration_ms = 5;
}
message UidProto {
// Combination of app ID and user ID.
optional int32 uid = 1;
- repeated string package_names = 2;
+
+ // The statistics associated with a particular package.
+ message Package {
+ optional string name = 1;
+
+ message Service {
+ optional string name = 1;
+ // Time spent started.
+ optional int64 start_duration_ms = 2;
+ optional int32 start_count = 3;
+ optional int32 launch_count = 4;
+ }
+ repeated Service services = 2;
+ }
+ repeated Package packages = 2;
+
+ optional ControllerActivityProto bluetooth_controller = 3;
+ optional ControllerActivityProto modem_controller = 4;
+ optional ControllerActivityProto wifi_controller = 5;
+
+ // Bluetooth misc data.
+ message BluetoothMisc {
+ // Duration spent BLE scanning blamed on this App (i.e. apportioned to this
+ // app amongst all apps doing BLE scanning; see explanation of 'apportioned'
+ // in App's comment).
+ optional TimerProto apportioned_ble_scan = 1;
+ // Background times aren't apportioned.
+ optional TimerProto background_ble_scan = 2;
+ // Running unoptimized BLE scanning, as defined by Bluetooth's
+ // AppScanStats.recordScanStart. As of May 2017, these are unfiltered,
+ // non-opportunistic, non-first-match scans. Durations are not
+ // pooled/apportioned.
+ optional TimerProto unoptimized_ble_scan = 3;
+ // Running unoptimized BLE scanning when app is in background. Durations are
+ // not pooled/apportioned.
+ optional TimerProto background_unoptimized_ble_scan = 4;
+ // Count of results returned by BLE scanning.
+ optional int32 ble_scan_result_count = 5;
+ // Count of results returned by BLE scans when app is in background.
+ // (Included in ble_scan_result_count.)
+ optional int32 background_ble_scan_result_count = 6;
+ }
+ optional BluetoothMisc bluetooth_misc = 6;
+
+ message Cpu {
+ // Total CPU time with processes executing in userspace. Summed up across
+ // multiple cores.
+ optional int64 user_duration_ms = 1;
+ // Total CPU time with processes executing kernel syscalls. Summed up across
+ // multiple cores.
+ optional int64 system_duration_ms = 2;
+
+ // CPU time broken down by CPU frequency (go/cpu-battery-metrics).
+ //
+ // These are real CPU time measurement from the kernel, so their sum can
+ // be different from the sum of user_duration_millis and
+ // system_duration_millis, which are just approximations. Data is not
+ // tracked when device is charging.
+ message ByFrequency {
+ // Index of the frequency in system.cpu_frequency. It starts from 1, to
+ // make it easier to analyze.
+ optional int32 frequency_index = 1;
+ // CPU time in milliseconds.
+ optional int64 total_duration_ms = 2;
+ // Screen-off CPU time in milliseconds.
+ optional int64 screen_off_duration_ms = 3;
+ }
+ repeated ByFrequency by_frequency = 3;
+ }
+ optional Cpu cpu = 7;
+
+ // Duration is pooled/apportioned.
+ optional TimerProto audio = 8;
+ // Duration is pooled/apportioned.
+ optional TimerProto camera = 9;
+ // Duration is pooled/apportioned.
+ optional TimerProto flashlight = 10;
+ // Duration is not pooled/apportioned.
+ optional TimerProto foreground_activity = 11;
+ // Duration is not pooled/apportioned.
+ optional TimerProto foreground_service = 12;
+ // Duration is not pooled/apportioned.
+ optional TimerProto vibrator = 13;
+ // Duration is pooled/apportioned.
+ optional TimerProto video = 14;
+
+ message Job {
+ optional string name = 1;
+ // Job times aren't apportioned.
+ optional TimerProto total = 2;
+ optional TimerProto background = 3;
+ }
+ repeated Job jobs = 15;
+
+ message JobCompletion {
+ // Job name.
+ optional string name = 1;
+
+ message ReasonCount {
+ optional android.app.JobParametersProto.CancelReason name = 1;
+ optional int32 count = 2;
+ }
+ repeated ReasonCount reason_count = 2;
+ };
+ repeated JobCompletion job_completion = 16;
+
+ message Network {
+ // Mobile data traffic (total, background + foreground).
+ optional int64 mobile_bytes_rx = 1;
+ optional int64 mobile_bytes_tx = 2;
+ // Wifi data traffic (total, background + foreground).
+ optional int64 wifi_bytes_rx = 3;
+ optional int64 wifi_bytes_tx = 4;
+ // Bluetooth data traffic (total, background + foreground).
+ optional int64 bt_bytes_rx = 5;
+ optional int64 bt_bytes_tx = 6;
+ // In packets (total, background + foreground).
+ optional int64 mobile_packets_rx = 7;
+ optional int64 mobile_packets_tx = 8;
+ optional int64 wifi_packets_rx = 9;
+ optional int64 wifi_packets_tx = 10;
+ // Radio active duration.
+ optional int64 mobile_active_duration_ms = 11;
+ optional int32 mobile_active_count = 12;
+ // Number of times the app woke up the mobile radio.
+ optional int32 mobile_wakeup_count = 13;
+ // Number of times the app woke up the wifi radio.
+ optional int32 wifi_wakeup_count = 14;
+ // Mobile data traffic in the background only, included in total above.
+ optional int64 mobile_bytes_bg_rx = 15;
+ optional int64 mobile_bytes_bg_tx = 16;
+ // Wifi data traffic in the background only, included in total above.
+ optional int64 wifi_bytes_bg_rx = 17;
+ optional int64 wifi_bytes_bg_tx = 18;
+ // In packets (background only, included in total packets above).
+ optional int64 mobile_packets_bg_rx = 19;
+ optional int64 mobile_packets_bg_tx = 20;
+ optional int64 wifi_packets_bg_rx = 21;
+ optional int64 wifi_packets_bg_tx = 22;
+ };
+ optional Network network = 17;
+
+ // TODO: combine System and App messages?
+ message PowerUseItem {
+ // Estimated power use in mAh.
+ optional double computed_power_mah = 1;
+ // Starting in Oreo, Battery Settings has two modes to display the battery
+ // info. The first is "app usage list". In this mode, items with should_hide
+ // enabled are hidden.
+ optional bool should_hide = 2;
+ // Smeared power from screen usage. Screen usage power is split and smeared
+ // among apps, based on activity time.
+ optional double screen_power_mah = 3;
+ // Smeared power using proportional method. Power usage from hidden sippers
+ // is smeared to all apps proportionally (except for screen usage).
+ optional double proportional_smear_mah = 4;
+ };
+ optional PowerUseItem power_use_item = 18;
+
+ // Durations are not pooled/apportioned.
+ message Process {
+ optional string name = 1;
+ // Time spent executing in user code.
+ optional int64 user_duration_ms = 2;
+ // Time spent executing in kernel code.
+ optional int64 system_duration_ms = 3;
+ // Time the process was running in the foreground.
+ optional int64 foreground_duration_ms = 4;
+ // Number of times the process has been started.
+ optional int32 start_count = 5;
+ // Number of times the process has had an ANR.
+ optional int32 anr_count = 6;
+ // Number of times the process has crashed.
+ optional int32 crash_count = 7;
+ };
+ repeated Process process = 19;
+
+ message StateTime {
+ // All of these (non-deprecated) states are mutually exclusive and can be
+ // added together to find the total time a uid has had any processes running
+ // at all.
+
+ // In approximate order or priority (top being what the framework considers
+ // most important and is thus least likely to kill when resources are
+ // needed:
+ // top > foreground service > top sleeping > foreground > background > cache
+ enum State {
+ // Time this uid has any processes in the top state (or above such as
+ // persistent).
+ PROCESS_STATE_TOP = 0;
+ // Time this uid has any process with a started out bound foreground
+ // service, but none in the "top" state.
+ PROCESS_STATE_FOREGROUND_SERVICE = 1;
+ // Time this uid has any process that is top while the device is sleeping,
+ // but none in the "foreground service" or better state. Sleeping is
+ // mostly screen off, but also includes the time when the screen is on but
+ // the device has not yet been unlocked.
+ PROCESS_STATE_TOP_SLEEPING = 2;
+ // Time this uid has any process in an active foreground state, but none
+ // in the "top sleeping" or better state.
+ PROCESS_STATE_FOREGROUND = 3;
+ // Time this uid has any process in an active background state, but none
+ // in the "foreground" or better state.
+ PROCESS_STATE_BACKGROUND = 4;
+ // Time this uid has any processes that are sitting around cached, not in
+ // one of the other active states.
+ PROCESS_STATE_CACHED = 5;
+ }
+ optional State state = 1;
+ optional int64 duration_ms = 2;
+ }
+ repeated StateTime states = 20;
+
+ message Sensor {
+ optional int32 id = 1;
+ optional TimerProto apportioned = 2;
+ // Background times aren't apportioned.
+ optional TimerProto background = 3;
+ }
+ repeated Sensor sensors = 21;
+
+ message Sync {
+ optional string name = 1;
+ // Sync times aren't apportioned.
+ optional TimerProto total = 2;
+ optional TimerProto background = 3;
+ }
+ repeated Sync syncs = 22;
+
+ message UserActivity {
+ optional android.os.PowerManagerProto.UserActivityEvent name = 1;
+ optional int32 count = 2;
+ };
+ repeated UserActivity user_activity = 23;
+
+ // Aggregated wakelock data for an app overall, across all of its wakelocks.
+ // The Wakelock message holds data about each *individual* wakelock, but it
+ // cannot be used to ascertain the aggregated time the app spent holding
+ // wakelocks, since merely summing Wakelock data will either underestimate (in
+ // the case of wakelock.partial.duration_ms) or overestimate (in the case of
+ // wakelock.partial.total_duration_ms) the total time, due to overlapping
+ // wakelocks. AggregatedWakelock, on the other hand, holds overall per-app
+ // wakelock data.
+ message AggregatedWakelock {
+ // The total duration that the app spent holding partial wakelocks.
+ // It includes both foreground + background use.
+ optional int64 partial_duration_ms = 1;
+ // The total duration that the app spent holding partial wakelocks while the
+ // app was in the background. Subtracting from partial_duration_ms will
+ // yield foreground usage.
+ optional int64 background_partial_duration_ms = 2;
+ };
+ optional AggregatedWakelock aggregated_wakelock = 24;
+
+ message Wakelock {
+ optional string name = 1;
+
+ // Full wakelocks keep the screen on. Based on
+ // PowerManager.SCREEN_BRIGHT_WAKE_LOCK (deprecated in API 13) and
+ // PowerManager.SCREEN_DIM_WAKE_LOCK (deprecated in API 17). Current, max,
+ // and total durations are not tracked for full wakelocks.
+ optional TimerProto full = 2;
+
+ // Partial wakelocks ensure the CPU is running while allowing the screen
+ // to turn off. Based on PowerManager.PARTIAL_WAKE_LOCK.
+ // Partial wakelock metrics are only recorded when the device is unplugged
+ // *and* the screen is off. Current, max, and total durations are tracked
+ // for partial wakelocks.
+ optional TimerProto partial = 3;
+
+ // These fields are for tracking partial wakelocks (see above), but only
+ // the time the wakelock was held while the app was in a background state.
+ // Since all background tracking is 'actual', not 'apportioned',
+ // background_partial.duration_ms is identical to
+ // background_partial.total_duration_ms.
+ optional TimerProto background_partial = 4;
+
+ // Window wakelocks keep the screen on. Current, max, and total durations
+ // are not tracked for window wakelocks.
+ optional TimerProto window = 5;
+ };
+ repeated Wakelock wakelocks = 25;
+
+ message WakeupAlarm {
+ // Wakeup alarm name.
+ optional string name = 1;
+ // Only includes counts when screen-off (& on battery).
+ optional int32 count = 2;
+ }
+ repeated WakeupAlarm wakeup_alarm = 26;
+
+ message Wifi {
+ // Duration holding Wifi-lock. This time is apportioned.
+ optional int64 full_wifi_lock_duration_ms = 1;
+ // Duration running Wifi. This time is apportioned.
+ optional int64 running_duration_ms = 2;
+ // Duration performing Wifi-scan blamed on this App (i.e. apportioned to
+ // this app amongst all apps doing Wifi-scanning; see explanation of
+ // 'apportioned' in App's comment).
+ optional TimerProto apportioned_scan = 3;
+ // Scans performed when app is in background. (Included in
+ // apportioned_scan). This value is not apportioned. Subtracting
+ // background_scan.total_duration_ms from apportioned_scan.total_duration_ms
+ // will yield foreground usage.
+ optional TimerProto background_scan = 4;
+ };
+ optional Wifi wifi = 27;
}
diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto
new file mode 100644
index 0000000..3bfe5d6
--- /dev/null
+++ b/core/proto/android/os/powermanager.proto
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.os;
+
+message PowerManagerProto {
+ /* User activity events in PowerManager.java. */
+ enum UserActivityEvent {
+ // Unspecified event type.
+ USER_ACTIVITY_EVENT_OTHER = 0;
+ // Button or key pressed or released.
+ USER_ACTIVITY_EVENT_BUTTON = 1;
+ // Touch down, move or up.
+ USER_ACTIVITY_EVENT_TOUCH = 2;
+ // Accessibility taking action on behalf of user.
+ USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
+ }
+}
diff --git a/core/res/res/layout/floating_popup_menu_button.xml b/core/res/res/layout/floating_popup_menu_button.xml
index 6cbe8c8..c419e46 100644
--- a/core/res/res/layout/floating_popup_menu_button.xml
+++ b/core/res/res/layout/floating_popup_menu_button.xml
@@ -53,7 +53,6 @@
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:textSize="@dimen/floating_toolbar_text_size"
- android:textAllCaps="true"
android:textColor="?attr/floatingToolbarForegroundColor"
android:background="@null"
android:focusable="false"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dec5fd9..571aad9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3128,4 +3128,7 @@
<!-- Class names of device specific services inheriting com.android.server.SystemService. The
classes are instantiated in the order of the array. -->
<string-array translatable="false" name="config_deviceSpecificSystemServices"></string-array>
+
+ <!-- Component name of media projection permission dialog -->
+ <string name="config_mediaProjectionPermissionDialogComponent" translateable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 93c02b0..78a8e2a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4346,8 +4346,6 @@
<string name="lock_to_app_toast">To unpin this screen, touch & hold Back and Overview
buttons</string>
- <!-- Notify user that they are locked in lock-to-app mode -->
- <string name="lock_to_app_toast_locked">This app can\'t be unpinned</string>
<!-- Starting lock-to-app indication. -->
<string name="lock_to_app_start">Screen pinned</string>
<!-- Exting lock-to-app indication. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a12187e..1627a0e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -754,7 +754,6 @@
<java-symbol type="string" name="last_month" />
<java-symbol type="string" name="launchBrowserDefault" />
<java-symbol type="string" name="lock_to_app_toast" />
- <java-symbol type="string" name="lock_to_app_toast_locked" />
<java-symbol type="string" name="lock_to_app_start" />
<java-symbol type="string" name="lock_to_app_exit" />
<java-symbol type="string" name="lock_to_app_unlock_pin" />
@@ -3125,4 +3124,7 @@
<java-symbol type="string" name="shortcut_restore_not_supported" />
<java-symbol type="string" name="shortcut_restore_signature_mismatch" />
<java-symbol type="string" name="shortcut_restore_unknown_issue" />
+
+ <!-- From media projection -->
+ <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
</resources>
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
deleted file mode 100644
index 1bad6fe..0000000
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
+++ /dev/null
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
deleted file mode 100644
index 0cf0f79..0000000
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
+++ /dev/null
@@ -1,207 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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.
--->
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
-
- <GlyphOrder>
- <GlyphID id="0" name=".notdef"/>
- <GlyphID id="1" name="0em"/>
- <GlyphID id="2" name="1em"/>
- <GlyphID id="3" name="3em"/>
- <GlyphID id="4" name="5em"/>
- <GlyphID id="5" name="7em"/>
- <GlyphID id="6" name="10em"/>
- <GlyphID id="7" name="50em"/>
- <GlyphID id="8" name="100em"/>
- </GlyphOrder>
-
- <head>
- <tableVersion value="1.0"/>
- <fontRevision value="1.0"/>
- <checkSumAdjustment value="0x640cdb2f"/>
- <magicNumber value="0x5f0f3cf5"/>
- <flags value="00000000 00000011"/>
- <unitsPerEm value="100"/>
- <created value="Fri Mar 17 07:26:00 2017"/>
- <macStyle value="00000000 00000000"/>
- <lowestRecPPEM value="7"/>
- <fontDirectionHint value="2"/>
- <glyphDataFormat value="0"/>
- </head>
-
- <hhea>
- <tableVersion value="0x00010000"/>
- <ascent value="1000"/>
- <descent value="-200"/>
- <lineGap value="0"/>
- <caretSlopeRise value="1"/>
- <caretSlopeRun value="0"/>
- <caretOffset value="0"/>
- <reserved0 value="0"/>
- <reserved1 value="0"/>
- <reserved2 value="0"/>
- <reserved3 value="0"/>
- <metricDataFormat value="0"/>
- </hhea>
-
- <maxp>
- <tableVersion value="0x10000"/>
- <maxZones value="0"/>
- <maxTwilightPoints value="0"/>
- <maxStorage value="0"/>
- <maxFunctionDefs value="0"/>
- <maxInstructionDefs value="0"/>
- <maxStackElements value="0"/>
- <maxSizeOfInstructions value="0"/>
- <maxComponentElements value="0"/>
- </maxp>
-
- <OS_2>
- <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
- will be recalculated by the compiler -->
- <version value="3"/>
- <xAvgCharWidth value="594"/>
- <usWeightClass value="400"/>
- <usWidthClass value="5"/>
- <fsType value="00000000 00001000"/>
- <ySubscriptXSize value="650"/>
- <ySubscriptYSize value="600"/>
- <ySubscriptXOffset value="0"/>
- <ySubscriptYOffset value="75"/>
- <ySuperscriptXSize value="650"/>
- <ySuperscriptYSize value="600"/>
- <ySuperscriptXOffset value="0"/>
- <ySuperscriptYOffset value="350"/>
- <yStrikeoutSize value="50"/>
- <yStrikeoutPosition value="300"/>
- <sFamilyClass value="0"/>
- <panose>
- <bFamilyType value="0"/>
- <bSerifStyle value="0"/>
- <bWeight value="5"/>
- <bProportion value="0"/>
- <bContrast value="0"/>
- <bStrokeVariation value="0"/>
- <bArmStyle value="0"/>
- <bLetterForm value="0"/>
- <bMidline value="0"/>
- <bXHeight value="0"/>
- </panose>
- <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
- <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
- <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
- <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
- <achVendID value="UKWN"/>
- <fsSelection value="00000000 01000000"/>
- <usFirstCharIndex value="32"/>
- <usLastCharIndex value="122"/>
- <sTypoAscender value="800"/>
- <sTypoDescender value="-200"/>
- <sTypoLineGap value="200"/>
- <usWinAscent value="1000"/>
- <usWinDescent value="200"/>
- <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
- <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
- <sxHeight value="500"/>
- <sCapHeight value="700"/>
- <usDefaultChar value="0"/>
- <usBreakChar value="32"/>
- <usMaxContext value="0"/>
- </OS_2>
-
- <hmtx>
- <mtx name=".notdef" width="50" lsb="0"/>
- <mtx name="0em" width="0" lsb="0"/>
- <mtx name="1em" width="100" lsb="0"/>
- <mtx name="3em" width="300" lsb="0"/>
- <mtx name="5em" width="500" lsb="0"/>
- <mtx name="7em" width="700" lsb="0"/>
- <mtx name="10em" width="1000" lsb="0"/>
- <mtx name="50em" width="5000" lsb="0"/>
- <mtx name="100em" width="10000" lsb="0"/>
- </hmtx>
-
- <cmap>
- <tableVersion version="0"/>
- <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
- <map code="0x0020" name="10em" />
- <map code="0x002e" name="10em" /> <!-- . -->
- <map code="0x0043" name="100em" /> <!-- C -->
- <map code="0x0049" name="1em" /> <!-- I -->
- <map code="0x004c" name="50em" /> <!-- L -->
- <map code="0x0056" name="5em" /> <!-- V -->
- <map code="0x0058" name="10em" /> <!-- X -->
- <map code="0x005f" name="0em" /> <!-- _ -->
- <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
- <map code="0x10331" name="10em" />
- </cmap_format_12>
- </cmap>
-
- <loca>
- <!-- The 'loca' table will be calculated by the compiler -->
- </loca>
-
- <glyf>
- <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="0em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
- </glyf>
-
- <name>
- <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Font for StaticLayoutLineBreakingTest
- </namerecord>
- <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Regular
- </namerecord>
- <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Font for StaticLayoutLineBreakingTest
- </namerecord>
- <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
- SampleFont-Regular
- </namerecord>
- <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
- Sample Font
- </namerecord>
- <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
- Regular
- </namerecord>
- <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
- Sample Font
- </namerecord>
- <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
- SampleFont-Regular
- </namerecord>
- </name>
-
- <post>
- <formatType value="3.0"/>
- <italicAngle value="0.0"/>
- <underlinePosition value="-75"/>
- <underlineThickness value="50"/>
- <isFixedPitch value="0"/>
- <minMemType42 value="0"/>
- <maxMemType42 value="0"/>
- <minMemType1 value="0"/>
- <maxMemType1 value="0"/>
- </post>
-
-</ttFont>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 6c32590..ebe0527 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -38,7 +38,6 @@
import java.util.Set;
/** Tests that ensure appropriate settings are backed up. */
-@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SettingsBackupTest {
@@ -181,6 +180,7 @@
Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+ Settings.Global.DNS_TLS_DISABLED,
Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java b/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
index c158b9f..ab541a1 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
@@ -16,11 +16,23 @@
package android.service.settings.suggestions;
+import android.support.annotation.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.List;
public class MockSuggestionService extends SuggestionService {
+ @VisibleForTesting
+ static boolean sOnSuggestionLaunchedCalled;
+ @VisibleForTesting
+ static boolean sOnSuggestionDismissedCalled;
+
+ public static void reset() {
+ sOnSuggestionLaunchedCalled = false;
+ sOnSuggestionDismissedCalled = false;
+ }
+
@Override
public List<Suggestion> onGetSuggestions() {
final List<Suggestion> data = new ArrayList<>();
@@ -34,5 +46,11 @@
@Override
public void onSuggestionDismissed(Suggestion suggestion) {
+ sOnSuggestionDismissedCalled = true;
+ }
+
+ @Override
+ public void onSuggestionLaunched(Suggestion suggestion) {
+ sOnSuggestionLaunchedCalled = true;
}
}
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
index bc88231..dca8c09 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
@@ -16,12 +16,17 @@
package android.service.settings.suggestions;
+import static com.google.common.truth.Truth.assertThat;
+
import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ServiceTestRule;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -45,9 +50,36 @@
MockSuggestionService.class);
}
+ @After
+ public void tearUp() {
+ MockSuggestionService.reset();
+ }
+
@Test
public void canStartService() throws TimeoutException {
mServiceTestRule.startService(mMockServiceIntent);
// Do nothing after starting service.
}
+
+ @Test
+ public void dismissSuggestion_shouldCallImplementation()
+ throws TimeoutException, RemoteException {
+ MockSuggestionService service = new MockSuggestionService();
+ IBinder binder = mServiceTestRule.bindService(mMockServiceIntent);
+ ISuggestionService serviceBinder = ISuggestionService.Stub.asInterface(binder);
+ serviceBinder.dismissSuggestion(null);
+
+ assertThat(service.sOnSuggestionDismissedCalled).isTrue();
+ }
+
+ @Test
+ public void launchSuggestion_shouldCallImplementation()
+ throws TimeoutException, RemoteException {
+ MockSuggestionService service = new MockSuggestionService();
+ IBinder binder = mServiceTestRule.bindService(mMockServiceIntent);
+ ISuggestionService serviceBinder = ISuggestionService.Stub.asInterface(binder);
+ serviceBinder.launchSuggestion(null);
+
+ assertThat(service.sOnSuggestionLaunchedCalled).isTrue();
+ }
}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index d16cce8..8092203 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -96,7 +96,7 @@
int n = chs.length;
byte[] chInfo = new byte[n];
- int resultDir = AndroidBidi.bidi(dir, chs, chInfo, n, false);
+ int resultDir = AndroidBidi.bidi(dir, chs, chInfo);
{
StringBuilder sb = new StringBuilder("info:");
diff --git a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
deleted file mode 100644
index f7dbafa..0000000
--- a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2012 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 static org.junit.Assert.assertEquals;
-
-import android.content.Context;;
-import android.graphics.Typeface;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.text.Layout.Alignment;
-import android.text.style.MetricAffectingSpan;
-import android.util.Log;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class StaticLayoutLineBreakingTest {
- // Span test are currently not supported because text measurement uses the MeasuredText
- // internal mWorkPaint instead of the provided MockTestPaint.
- private static final boolean SPAN_TESTS_SUPPORTED = false;
- private static final boolean DEBUG = false;
-
- private static final float SPACE_MULTI = 1.0f;
- private static final float SPACE_ADD = 0.0f;
- private static final int WIDTH = 100;
- private static final Alignment ALIGN = Alignment.ALIGN_LEFT;
-
- private static final char SURR_FIRST = '\uD800';
- private static final char SURR_SECOND = '\uDF31';
-
- private static final int[] NO_BREAK = new int[] {};
-
- private static final TextPaint sTextPaint = new TextPaint();
-
- static {
- // The test font has following coverage and width.
- // U+0020: 10em
- // U+002E (.): 10em
- // U+0043 (C): 100em
- // U+0049 (I): 1em
- // U+004C (L): 50em
- // U+0056 (V): 5em
- // U+0058 (X): 10em
- // U+005F (_): 0em
- // U+FFFD (invalid surrogate will be replaced to this): 7em
- // U+10331 (\uD800\uDF31): 10em
- Context context = InstrumentationRegistry.getTargetContext();
- sTextPaint.setTypeface(Typeface.createFromAsset(context.getAssets(),
- "fonts/StaticLayoutLineBreakingTestFont.ttf"));
- sTextPaint.setTextSize(1.0f); // Make 1em == 1px.
- }
-
- private static StaticLayout getStaticLayout(CharSequence source, int width) {
- return new StaticLayout(source, sTextPaint, width, ALIGN, SPACE_MULTI, SPACE_ADD, false);
- }
-
- private static int[] getBreaks(CharSequence source) {
- return getBreaks(source, WIDTH);
- }
-
- private static int[] getBreaks(CharSequence source, int width) {
- StaticLayout staticLayout = getStaticLayout(source, width);
-
- int[] breaks = new int[staticLayout.getLineCount() - 1];
- for (int line = 0; line < breaks.length; line++) {
- breaks[line] = staticLayout.getLineEnd(line);
- }
- return breaks;
- }
-
- private static void debugLayout(CharSequence source, StaticLayout staticLayout) {
- if (DEBUG) {
- int count = staticLayout.getLineCount();
- Log.i("SLLBTest", "\"" + source.toString() + "\": "
- + count + " lines");
- for (int line = 0; line < count; line++) {
- int lineStart = staticLayout.getLineStart(line);
- int lineEnd = staticLayout.getLineEnd(line);
- Log.i("SLLBTest", "Line " + line + " [" + lineStart + ".."
- + lineEnd + "]\t" + source.subSequence(lineStart, lineEnd));
- }
- }
- }
-
- private static void layout(CharSequence source, int[] breaks) {
- layout(source, breaks, WIDTH);
- }
-
- private static void layout(CharSequence source, int[] breaks, int width) {
- StaticLayout staticLayout = getStaticLayout(source, width);
-
- debugLayout(source, staticLayout);
-
- int lineCount = breaks.length + 1;
- assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
-
- for (int line = 0; line < lineCount; line++) {
- int lineStart = staticLayout.getLineStart(line);
- int lineEnd = staticLayout.getLineEnd(line);
-
- if (line == 0) {
- assertEquals("Line start for first line", 0, lineStart);
- } else {
- assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
- }
-
- if (line == lineCount - 1) {
- assertEquals("Line end for last line", source.length(), lineEnd);
- } else {
- assertEquals("Line end for line " + line, breaks[line], lineEnd);
- }
- }
- }
-
- private static void layoutMaxLines(CharSequence source, int[] breaks, int maxLines) {
- StaticLayout staticLayout = new StaticLayout(source, 0, source.length(), sTextPaint, WIDTH,
- ALIGN, TextDirectionHeuristics.LTR, SPACE_MULTI, SPACE_ADD, false /* includePad */,
- null, WIDTH, maxLines);
-
- debugLayout(source, staticLayout);
-
- final int lineCount = staticLayout.getLineCount();
-
- for (int line = 0; line < lineCount; line++) {
- int lineStart = staticLayout.getLineStart(line);
- int lineEnd = staticLayout.getLineEnd(line);
-
- if (line == 0) {
- assertEquals("Line start for first line", 0, lineStart);
- } else {
- assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
- }
-
- if (line == lineCount - 1 && line != breaks.length - 1) {
- assertEquals("Line end for last line", source.length(), lineEnd);
- } else {
- assertEquals("Line end for line " + line, breaks[line], lineEnd);
- }
- }
- }
-
- private static final int MAX_SPAN_COUNT = 10;
- private static final int[] sSpanStarts = new int[MAX_SPAN_COUNT];
- private static final int[] sSpanEnds = new int[MAX_SPAN_COUNT];
-
- private static MetricAffectingSpan getMetricAffectingSpan() {
- return new MetricAffectingSpan() {
- @Override
- public void updateDrawState(TextPaint tp) { /* empty */ }
-
- @Override
- public void updateMeasureState(TextPaint p) { /* empty */ }
- };
- }
-
- /**
- * Replaces the "<...>" blocks by spans, assuming non overlapping, correctly defined spans
- * @param text
- * @return A CharSequence with '<' '>' replaced by MetricAffectingSpan
- */
- private static CharSequence spanify(String text) {
- int startIndex = text.indexOf('<');
- if (startIndex < 0) return text;
-
- int spanCount = 0;
- do {
- int endIndex = text.indexOf('>');
- if (endIndex < 0) throw new IllegalArgumentException("Unbalanced span markers");
-
- text = text.substring(0, startIndex) + text.substring(startIndex + 1, endIndex)
- + text.substring(endIndex + 1);
-
- sSpanStarts[spanCount] = startIndex;
- sSpanEnds[spanCount] = endIndex - 2;
- spanCount++;
-
- startIndex = text.indexOf('<');
- } while (startIndex >= 0);
-
- SpannableStringBuilder result = new SpannableStringBuilder(text);
- for (int i = 0; i < spanCount; i++) {
- result.setSpan(getMetricAffectingSpan(), sSpanStarts[i], sSpanEnds[i],
- Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
- return result;
- }
-
- @Test
- public void testNoLineBreak() {
- // Width lower than WIDTH
- layout("", NO_BREAK);
- layout("I", NO_BREAK);
- layout("V", NO_BREAK);
- layout("X", NO_BREAK);
- layout("L", NO_BREAK);
- layout("I VILI", NO_BREAK);
- layout("XXXX", NO_BREAK);
- layout("LXXXX", NO_BREAK);
-
- // Width equal to WIDTH
- layout("C", NO_BREAK);
- layout("LL", NO_BREAK);
- layout("L XXXX", NO_BREAK);
- layout("XXXXXXXXXX", NO_BREAK);
- layout("XXX XXXXXX", NO_BREAK);
- layout("XXX XXXX X", NO_BREAK);
- layout("XXX XXXXX ", NO_BREAK);
- layout(" XXXXXXXX ", NO_BREAK);
- layout(" XX XXX ", NO_BREAK);
- // 0123456789
-
- // Width greater than WIDTH, but no break
- layout(" XX XXX ", NO_BREAK);
- layout("XX XXX XXX ", NO_BREAK);
- layout("XX XXX XXX ", NO_BREAK);
- layout("XXXXXXXXXX ", NO_BREAK);
- // 01234567890
- }
-
- @Test
- public void testOneLineBreak() {
- // 01234567890
- layout("XX XXX XXXX", new int[] {7});
- layout("XX XXXX XXX", new int[] {8});
- layout("XX XXXXX XX", new int[] {9});
- layout("XX XXXXXX X", new int[] {10});
- // 01234567890
- layout("XXXXXXXXXXX", new int[] {10});
- layout("XXXXXXXXX X", new int[] {10});
- layout("XXXXXXXX XX", new int[] {9});
- layout("XXXXXXX XXX", new int[] {8});
- layout("XXXXXX XXXX", new int[] {7});
- // 01234567890
- layout("LL LL", new int[] {3});
- layout("LLLL", new int[] {2});
- layout("C C", new int[] {2});
- layout("CC", new int[] {1});
- }
-
- @Test
- public void testSpaceAtBreak() {
- // 0123456789012
- layout("XXXX XXXXX X", new int[] {11});
- layout("XXXXXXXXXX X", new int[] {11});
- layout("XXXXXXXXXV X", new int[] {11});
- layout("C X", new int[] {2});
- }
-
- @Test
- public void testMultipleSpacesAtBreak() {
- // 0123456789012
- layout("LXX XXXX", new int[] {4});
- layout("LXX XXXX", new int[] {5});
- layout("LXX XXXX", new int[] {6});
- layout("LXX XXXX", new int[] {7});
- layout("LXX XXXX", new int[] {8});
- }
-
- @Test
- public void testZeroWidthCharacters() {
- // 0123456789012345678901234
- layout("X_X_X_X_X_X_X_X_X_X", NO_BREAK);
- layout("___X_X_X_X_X_X_X_X_X_X___", NO_BREAK);
- layout("C_X", new int[] {2});
- layout("C__X", new int[] {3});
- }
-
- /**
- * Note that when the text has spans, StaticLayout does not use the provided TextPaint to
- * measure text runs anymore. This is probably a bug.
- * To be able to use the fake sTextPaint and make this test pass, use mPaint instead of
- * mWorkPaint in MeasuredText#addStyleRun
- */
- @Test
- public void testWithSpans() {
- if (!SPAN_TESTS_SUPPORTED) return;
-
- layout(spanify("<012 456 89>"), NO_BREAK);
- layout(spanify("012 <456> 89"), NO_BREAK);
- layout(spanify("<012> <456>< 89>"), NO_BREAK);
- layout(spanify("<012> <456> <89>"), NO_BREAK);
-
- layout(spanify("<012> <456> <89>012"), new int[] {8});
- layout(spanify("<012> <456> 89<012>"), new int[] {8});
- layout(spanify("<012> <456> <89><012>"), new int[] {8});
- layout(spanify("<012> <456> 89 <123>"), new int[] {11});
- layout(spanify("<012> <456> 89< 123>"), new int[] {11});
- layout(spanify("<012> <456> <89> <123>"), new int[] {11});
- layout(spanify("012 456 89 <LXX> XX XX"), new int[] {11, 18});
- }
-
- /*
- * Adding a span to the string should not change the layout, since the metrics are unchanged.
- */
- @Test
- public void testWithOneSpan() {
- if (!SPAN_TESTS_SUPPORTED) return;
-
- String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
- "012 456 89012 456 89012", "0123456789012" };
-
- MetricAffectingSpan metricAffectingSpan = getMetricAffectingSpan();
-
- for (String text : texts) {
- // Get the line breaks without any span
- int[] breaks = getBreaks(text);
-
- // Add spans on all possible offsets
- for (int spanStart = 0; spanStart < text.length(); spanStart++) {
- for (int spanEnd = spanStart; spanEnd < text.length(); spanEnd++) {
- SpannableStringBuilder ssb = new SpannableStringBuilder(text);
- ssb.setSpan(metricAffectingSpan, spanStart, spanEnd,
- Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- layout(ssb, breaks);
- }
- }
- }
- }
-
- @Test
- public void testWithTwoSpans() {
- if (!SPAN_TESTS_SUPPORTED) return;
-
- String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
- "012 456 89012 456 89012", "0123456789012" };
-
- MetricAffectingSpan metricAffectingSpan1 = getMetricAffectingSpan();
- MetricAffectingSpan metricAffectingSpan2 = getMetricAffectingSpan();
-
- for (String text : texts) {
- // Get the line breaks without any span
- int[] breaks = getBreaks(text);
-
- // Add spans on all possible offsets
- for (int spanStart1 = 0; spanStart1 < text.length(); spanStart1++) {
- for (int spanEnd1 = spanStart1; spanEnd1 < text.length(); spanEnd1++) {
- SpannableStringBuilder ssb = new SpannableStringBuilder(text);
- ssb.setSpan(metricAffectingSpan1, spanStart1, spanEnd1,
- Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
- for (int spanStart2 = 0; spanStart2 < text.length(); spanStart2++) {
- for (int spanEnd2 = spanStart2; spanEnd2 < text.length(); spanEnd2++) {
- ssb.setSpan(metricAffectingSpan2, spanStart2, spanEnd2,
- Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- layout(ssb, breaks);
- }
- }
- }
- }
- }
- }
-
- public static String replace(String string, char c, char r) {
- return string.replaceAll(String.valueOf(c), String.valueOf(r));
- }
-
- @Test
- public void testWithSurrogate() {
- layout("LX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
- layout("LXXXX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
- // LXXXXI (91) + SURR_FIRST + SURR_SECOND (10). Do not break in the middle point of
- // surrogatge pair.
- layout("LXXXXI" + SURR_FIRST + SURR_SECOND, new int[] {6});
-
- // LXXXXI (91) + SURR_SECOND (replaced with REPLACEMENT CHARACTER. width is 7px) fits.
- // Break just after invalid trailing surrogate.
- layout("LXXXXI" + SURR_SECOND + SURR_FIRST, new int[] {7});
-
- layout("C" + SURR_FIRST + SURR_SECOND, new int[] {1});
- }
-
- @Test
- public void testNarrowWidth() {
- int[] widths = new int[] { 0, 4, 10 };
- String[] texts = new String[] { "", "X", " ", "XX", " X", "XXX" };
-
- for (String text: texts) {
- // 15 is such that only one character will fit
- int[] breaks = getBreaks(text, 15);
-
- // Width under 15 should all lead to the same line break
- for (int width: widths) {
- layout(text, breaks, width);
- }
- }
- }
-
- @Test
- public void testNarrowWidthZeroWidth() {
- int[] widths = new int[] { 1, 4 };
- for (int width: widths) {
- layout("X.", new int[] {1}, width);
- layout("X__", NO_BREAK, width);
- layout("X__X", new int[] {3}, width);
- layout("X__X_", new int[] {3}, width);
-
- layout("_", NO_BREAK, width);
- layout("__", NO_BREAK, width);
- layout("_X", new int[] {1}, width);
- layout("_X_", new int[] {1}, width);
- layout("__X__", new int[] {2}, width);
- }
- }
-
- @Test
- public void testMaxLines() {
- layoutMaxLines("C", NO_BREAK, 1);
- layoutMaxLines("C C", new int[] {2}, 1);
- layoutMaxLines("C C", new int[] {2}, 2);
- layoutMaxLines("CC", new int[] {1}, 1);
- layoutMaxLines("CC", new int[] {1}, 2);
- }
-}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
new file mode 100644
index 0000000..06b860a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IServiceConnection;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import com.android.internal.widget.IRemoteViewsFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * Tests for RemoteViewsAdapter.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteViewsAdapterTest {
+
+ @Mock AppWidgetManager mAppWidgetManager;
+ @Mock IServiceConnection mIServiceConnection;
+ @Mock RemoteViewsAdapter.RemoteAdapterConnectionCallback mCallback;
+
+ private Handler mMainHandler;
+ private TestContext mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mAppWidgetManager
+ .bindRemoteViewsService(any(), anyInt(), any(), any(), anyInt())).thenReturn(true);
+ mContext = new TestContext();
+ mMainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Test
+ public void onRemoteAdapterConnected_after_metadata_loaded() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+ assertFalse(adapter.isDataReady());
+
+ assertNotNull(mContext.conn.get());
+
+ ViewsFactory factory = new ViewsFactory(1);
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mMainHandler);
+ verify(mCallback, never()).onRemoteAdapterConnected();
+
+ factory.loadingView.set(createViews("loading"));
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ verify(mCallback, times(1)).onRemoteAdapterConnected();
+
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ // Service is unbound
+ assertTrue(isUnboundOrScheduled());
+ }
+
+ @Test
+ public void viewReplaced_after_mainView_loaded() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+ ViewsFactory factory = new ViewsFactory(1);
+ factory.loadingView.set(createViews("loading"));
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+
+ // Returned view contains the loading text
+ View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+ ArrayList<View> search = new ArrayList<>();
+ view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+ assertEquals(1, search.size());
+
+ // Send the final remoteViews
+ factory.views[0].set(createViews("updated"));
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+
+ // Existing view got updated with new text
+ search.clear();
+ view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+ assertTrue(search.isEmpty());
+ view.findViewsWithText(search, "updated", View.FIND_VIEWS_WITH_TEXT);
+ assertEquals(1, search.size());
+
+ // Service is unbound
+ assertTrue(isUnboundOrScheduled());
+ }
+
+ @Test
+ public void notifyDataSetChanged_deferred() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+ ViewsFactory factory = new ViewsFactory(1);
+ factory.loadingView.set(createViews("loading"));
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ // Reset the loading view so that next refresh is blocked
+ factory.loadingView = new LockedValue<>();
+ factory.mCount = 3;
+ DataSetObserver observer = mock(DataSetObserver.class);
+ getOnUiThread(() -> {
+ adapter.registerDataSetObserver(observer);
+ adapter.notifyDataSetChanged();
+ return null;
+ });
+
+ waitOnHandler(mMainHandler);
+ // Still giving the old values
+ verify(observer, never()).onChanged();
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ factory.loadingView.set(createViews("refreshed"));
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+
+ // When the service returns new data, UI is updated.
+ verify(observer, times(1)).onChanged();
+ assertEquals((Integer) 3, getOnUiThread(adapter::getCount));
+
+ // Service is unbound
+ assertTrue(isUnboundOrScheduled());
+ }
+
+ @Test
+ public void serviceDisconnected_before_getView() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+ ViewsFactory factory = new ViewsFactory(1);
+ factory.loadingView.set(createViews("loading"));
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ verify(mCallback, times(1)).onRemoteAdapterConnected();
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ // Unbind the service
+ ServiceConnection conn = mContext.conn.get();
+ getOnHandler(mContext.handler.get(), () -> {
+ conn.onServiceDisconnected(null);
+ return null;
+ });
+
+ // Returned view contains the loading text
+ View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+ ArrayList<View> search = new ArrayList<>();
+ view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+ assertEquals(1, search.size());
+
+ // Unbind is not scheduled
+ assertFalse(isUnboundOrScheduled());
+
+ mContext.sendConnect(factory);
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ verify(mCallback, times(2)).onRemoteAdapterConnected();
+ }
+
+ private RemoteViews createViews(String text) {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.remote_views_text);
+ views.setTextViewText(R.id.text, text);
+ return views;
+ }
+
+ private <T> T getOnUiThread(Supplier<T> supplier) throws Throwable {
+ return getOnHandler(mMainHandler, supplier);
+ }
+
+ private boolean isUnboundOrScheduled() throws Throwable {
+ Handler handler = mContext.handler.get();
+ return getOnHandler(handler, () -> mContext.boundCount == 0
+ || handler.hasMessages(RemoteViewsAdapter.MSG_UNBIND_SERVICE));
+ }
+
+ private static <T> T getOnHandler(Handler handler, Supplier<T> supplier) throws Throwable {
+ LockedValue<T> result = new LockedValue<>();
+ handler.post(() -> result.set(supplier.get()));
+ return result.get();
+ }
+
+ private class TestContext extends ContextWrapper {
+
+ public final LockedValue<ServiceConnection> conn = new LockedValue<>();
+ public final LockedValue<Handler> handler = new LockedValue<>();
+ public int boundCount;
+
+ TestContext() {
+ super(InstrumentationRegistry.getContext());
+ }
+
+ @Override
+ public void unbindService(ServiceConnection conn) {
+ boundCount--;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.APPWIDGET_SERVICE.equals(name)) {
+ return mAppWidgetManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ @Override
+ public IServiceConnection getServiceDispatcher(
+ ServiceConnection conn, Handler handler, int flags) {
+ this.conn.set(conn);
+ this.handler.set(handler);
+ boundCount++;
+ return mIServiceConnection;
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ public void sendConnect(ViewsFactory factory) throws Exception {
+ ServiceConnection connection = conn.get();
+ handler.get().post(() -> connection.onServiceConnected(null, factory.asBinder()));
+ }
+ }
+
+ private static void waitOnHandler(Handler handler) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ handler.post(() -> latch.countDown());
+ latch.await(20, TimeUnit.SECONDS);
+ }
+
+ private static class ViewsFactory extends IRemoteViewsFactory.Stub {
+
+ public LockedValue<RemoteViews> loadingView = new LockedValue<>();
+ public LockedValue<RemoteViews>[] views;
+
+ private int mCount;
+
+ ViewsFactory(int count) {
+ mCount = count;
+ views = new LockedValue[count];
+ for (int i = 0; i < count; i++) {
+ views[i] = new LockedValue<>();
+ }
+ }
+
+ @Override
+ public void onDataSetChanged() {}
+
+ @Override
+ public void onDataSetChangedAsync() { }
+
+ @Override
+ public void onDestroy(Intent intent) { }
+
+ @Override
+ public int getCount() throws RemoteException {
+ return mCount;
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) throws RemoteException {
+ try {
+ return views[position].get();
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ @Override
+ public RemoteViews getLoadingView() throws RemoteException {
+ try {
+ return loadingView.get();
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public boolean isCreated() {
+ return false;
+ }
+ }
+
+ private static class DistinctIntent extends Intent {
+
+ @Override
+ public boolean filterEquals(Intent other) {
+ return false;
+ }
+ }
+
+ private static class LockedValue<T> {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private T mValue;
+
+ public void set(T value) {
+ mValue = value;
+ mLatch.countDown();
+ }
+
+ public T get() throws Exception {
+ mLatch.await(10, TimeUnit.SECONDS);
+ return mValue;
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
index 7935880..734ebef 100644
--- a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
@@ -47,6 +47,7 @@
private static final int TEST_ARG2 = 182;
private static final Object TEST_OBJ = "hello";
+ @Mock Context mContext;
@Mock AlarmManager mAlarmManager;
WakeupMessage mMessage;
// Make a spy so that we can verify calls to it
@@ -86,13 +87,12 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- Context context = mock(Context.class);
- when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
// capture the listener for each AlarmManager.setExact call
doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), any(String.class),
mListenerCaptor.capture(), any(Handler.class));
- mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
+ mMessage = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
TEST_ARG2, TEST_OBJ);
}
@@ -168,4 +168,19 @@
verifyMessageDispatchedOnce();
}
+ /**
+ * Verify that a Runnable is scheduled and dispatched.
+ */
+ @Test
+ public void scheduleRunnable() {
+ final long when = 1011;
+ final Runnable runnable = mock(Runnable.class);
+ WakeupMessage dut = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, runnable);
+ dut.schedule(when);
+ verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
+ eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
+ mListenerCaptor.getValue().onAlarm();
+ verify(runnable, times(1)).run();
+ }
+
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 902b872..6f2b038 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -20,6 +20,10 @@
applications that come with the platform
-->
<permissions>
+ <privapp-permissions package="android.ext.services">
+ <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
+ </privapp-permissions>
+
<privapp-permissions package="com.android.backupconfirm">
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.CRYPT_KEEPER"/>
@@ -367,8 +371,4 @@
<permission name="android.permission.CONTROL_VPN"/>
</privapp-permissions>
- <privapp-permissions package="com.google.android.ext.services">
- <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
- </privapp-permissions>
-
</permissions>
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index d4d9dcb..83c82af 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -18,6 +18,7 @@
#include "androidfw/AssetManager2.h"
+#include <iterator>
#include <set>
#include "android-base/logging.h"
@@ -263,9 +264,7 @@
}
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
- bool stop_at_first_match, LoadedArscEntry* out_entry,
- ResTable_config* out_selected_config,
- uint32_t* out_flags) {
+ bool stop_at_first_match, FindEntryResult* out_entry) {
ATRACE_CALL();
// Might use this if density_override != 0.
@@ -294,30 +293,28 @@
return kInvalidCookie;
}
- LoadedArscEntry best_entry;
- ResTable_config best_config;
+ FindEntryResult best_entry;
ApkAssetsCookie best_cookie = kInvalidCookie;
uint32_t cumulated_flags = 0u;
const PackageGroup& package_group = package_groups_[idx];
const size_t package_count = package_group.packages_.size();
for (size_t i = 0; i < package_count; i++) {
- LoadedArscEntry current_entry;
- ResTable_config current_config;
- uint32_t current_flags = 0;
-
const LoadedPackage* loaded_package = package_group.packages_[i];
- if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry,
- ¤t_config, ¤t_flags)) {
+
+ FindEntryResult current_entry;
+ if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) {
continue;
}
- cumulated_flags |= current_flags;
+ cumulated_flags |= current_entry.type_flags;
- if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config) ||
- (loaded_package->IsOverlay() && current_config.compare(best_config) == 0)) {
+ 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_config = current_config;
best_cookie = package_group.cookies_[i];
if (stop_at_first_match) {
break;
@@ -331,19 +328,16 @@
*out_entry = best_entry;
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
- *out_selected_config = best_config;
- *out_flags = cumulated_flags;
+ out_entry->type_flags = cumulated_flags;
return best_cookie;
}
bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
ATRACE_CALL();
- LoadedArscEntry entry;
- ResTable_config config;
- uint32_t flags = 0u;
- ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
- true /* stop_at_first_match */, &entry, &config, &flags);
+ FindEntryResult entry;
+ ApkAssetsCookie cookie =
+ FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry);
if (cookie == kInvalidCookie) {
return false;
}
@@ -377,11 +371,14 @@
}
bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
- LoadedArscEntry entry;
- ResTable_config config;
- ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
- false /* stop_at_first_match */, &entry, &config, out_flags);
- return cookie != kInvalidCookie;
+ FindEntryResult entry;
+ ApkAssetsCookie cookie =
+ FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
+ if (cookie != kInvalidCookie) {
+ *out_flags = entry.type_flags;
+ return cookie;
+ }
+ return kInvalidCookie;
}
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
@@ -390,11 +387,9 @@
uint32_t* out_flags) {
ATRACE_CALL();
- LoadedArscEntry entry;
- ResTable_config config;
- uint32_t flags = 0u;
+ FindEntryResult entry;
ApkAssetsCookie cookie =
- FindEntry(resid, density_override, false /* stop_at_first_match */, &entry, &config, &flags);
+ FindEntry(resid, density_override, false /* stop_at_first_match */, &entry);
if (cookie == kInvalidCookie) {
return kInvalidCookie;
}
@@ -408,8 +403,8 @@
// 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 = config;
- *out_flags = flags;
+ *out_selected_config = *entry.config;
+ *out_flags = entry.type_flags;
return cookie;
}
@@ -420,8 +415,8 @@
// Convert the package ID to the runtime assigned package ID.
entry.dynamic_ref_table->lookupResourceValue(out_value);
- *out_selected_config = config;
- *out_flags = flags;
+ *out_selected_config = *entry.config;
+ *out_flags = entry.type_flags;
return cookie;
}
@@ -464,11 +459,9 @@
return cached_iter->second.get();
}
- LoadedArscEntry entry;
- ResTable_config config;
- uint32_t flags = 0u;
- ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
- false /* stop_at_first_match */, &entry, &config, &flags);
+ FindEntryResult entry;
+ ApkAssetsCookie cookie =
+ FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
if (cookie == kInvalidCookie) {
return nullptr;
}
@@ -506,13 +499,20 @@
}
}
new_entry->cookie = cookie;
- new_entry->value.copyFrom_dtoh(map_entry->value);
new_entry->key = new_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+ if (err != NO_ERROR) {
+ LOG(ERROR) << base::StringPrintf(
+ "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
+ new_entry->value.data, new_key);
+ return nullptr;
+ }
++new_entry;
}
- new_bag->type_spec_flags = flags;
+ new_bag->type_spec_flags = entry.type_flags;
new_bag->entry_count = static_cast<uint32_t>(entry_count);
ResolvedBag* result = new_bag.get();
cached_bags_[resid] = std::move(new_bag);
@@ -530,9 +530,6 @@
return nullptr;
}
- // Combine flags from the parent and our own bag.
- flags |= parent_bag->type_spec_flags;
-
// Create the max possible entries we can make. Once we construct the bag,
// we will realloc to fit to size.
const size_t max_count = parent_bag->entry_count + dtohl(map->count);
@@ -557,10 +554,17 @@
// Use the child key if it comes before the parent
// or is equal to the parent (overrides).
new_entry->cookie = cookie;
- new_entry->value.copyFrom_dtoh(map_entry->value);
new_entry->key = child_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+ if (err != NO_ERROR) {
+ LOG(ERROR) << base::StringPrintf(
+ "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
+ new_entry->value.data, child_key);
+ return nullptr;
+ }
++map_entry;
} else {
// Take the parent entry as-is.
@@ -585,10 +589,16 @@
}
}
new_entry->cookie = cookie;
- new_entry->value.copyFrom_dtoh(map_entry->value);
new_entry->key = new_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+ if (err != NO_ERROR) {
+ LOG(ERROR) << base::StringPrintf("Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.",
+ new_entry->value.dataType, new_entry->value.data, new_key);
+ return nullptr;
+ }
++map_entry;
++new_entry;
}
@@ -608,7 +618,8 @@
new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
}
- new_bag->type_spec_flags = flags;
+ // Combine flags from the parent and our own bag.
+ new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags;
new_bag->entry_count = static_cast<uint32_t>(actual_count);
ResolvedBag* result = new_bag.get();
cached_bags_[resid] = std::move(new_bag);
@@ -701,7 +712,37 @@
}
}
-std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); }
+std::unique_ptr<Theme> AssetManager2::NewTheme() {
+ return std::unique_ptr<Theme>(new Theme(this));
+}
+
+Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
+}
+
+Theme::~Theme() = default;
+
+namespace {
+
+struct ThemeEntry {
+ ApkAssetsCookie cookie;
+ uint32_t type_spec_flags;
+ Res_value value;
+};
+
+struct ThemeType {
+ int entry_count;
+ ThemeEntry entries[0];
+};
+
+constexpr size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
+
+} // namespace
+
+struct Theme::Package {
+ // Each element of Type will be a dynamically sized object
+ // allocated to have the entries stored contiguously with the Type.
+ std::array<util::unique_cptr<ThemeType>, kTypeCount> types;
+};
bool Theme::ApplyStyle(uint32_t resid, bool force) {
ATRACE_CALL();
@@ -714,71 +755,69 @@
// Merge the flags from this style.
type_spec_flags_ |= bag->type_spec_flags;
- // On the first iteration, verify the attribute IDs and
- // update the entry count in each type.
- const auto bag_iter_end = end(bag);
- for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+ int last_type_idx = -1;
+ int last_package_idx = -1;
+ Package* last_package = nullptr;
+ ThemeType* last_type = nullptr;
+
+ // Iterate backwards, because each bag is sorted in ascending key ID order, meaning we will only
+ // need to perform one resize per type.
+ using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>;
+ const auto bag_iter_end = reverse_bag_iterator(begin(bag));
+ for (auto bag_iter = reverse_bag_iterator(end(bag)); bag_iter != bag_iter_end; ++bag_iter) {
const uint32_t attr_resid = bag_iter->key;
- // If the resource ID passed in is not a style, the key can be
- // some other identifier that is not a resource ID.
+ // If the resource ID passed in is not a style, the key can be some other identifier that is not
+ // a resource ID. We should fail fast instead of operating with strange resource IDs.
if (!is_valid_resid(attr_resid)) {
return false;
}
- const uint32_t package_idx = get_package_id(attr_resid);
+ // We don't use the 0-based index for the type so that we can avoid doing ID validation
+ // upon lookup. Instead, we keep space for the type ID 0 in our data structures. Since
+ // the construction of this type is guarded with a resource ID check, it will never be
+ // populated, and querying type ID 0 will always fail.
+ const int package_idx = get_package_id(attr_resid);
+ const int type_idx = get_type_id(attr_resid);
+ const int entry_idx = get_entry_id(attr_resid);
- // The type ID is 1-based, so subtract 1 to get an index.
- const uint32_t type_idx = get_type_id(attr_resid) - 1;
- const uint32_t entry_idx = get_entry_id(attr_resid);
-
- std::unique_ptr<Package>& package = packages_[package_idx];
- if (package == nullptr) {
- package.reset(new Package());
- }
-
- util::unique_cptr<Type>& type = package->types[type_idx];
- if (type == nullptr) {
- // Set the initial capacity to take up a total amount of 1024 bytes.
- constexpr uint32_t kInitialCapacity = (1024u - sizeof(Type)) / sizeof(Entry);
- const uint32_t initial_capacity = std::max(entry_idx, kInitialCapacity);
- type.reset(
- reinterpret_cast<Type*>(calloc(sizeof(Type) + (initial_capacity * sizeof(Entry)), 1)));
- type->entry_capacity = initial_capacity;
- }
-
- // Set the entry_count to include this entry. We will populate
- // and resize the array as necessary in the next pass.
- if (entry_idx + 1 > type->entry_count) {
- // Increase the entry count to include this.
- type->entry_count = entry_idx + 1;
- }
- }
-
- // On the second pass, we will realloc to fit the entry counts
- // and populate the structures.
- for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
- const uint32_t attr_resid = bag_iter->key;
- const uint32_t package_idx = get_package_id(attr_resid);
- const uint32_t type_idx = get_type_id(attr_resid) - 1;
- const uint32_t entry_idx = get_entry_id(attr_resid);
- Package* package = packages_[package_idx].get();
- util::unique_cptr<Type>& type = package->types[type_idx];
- if (type->entry_count != type->entry_capacity) {
- // Resize to fit the actual entries that will be included.
- Type* type_ptr = type.release();
- type.reset(reinterpret_cast<Type*>(
- realloc(type_ptr, sizeof(Type) + (type_ptr->entry_count * sizeof(Entry)))));
- if (type->entry_capacity < type->entry_count) {
- // Clear the newly allocated memory (which does not get zero initialized).
- // We need to do this because we |= type_spec_flags.
- memset(type->entries + type->entry_capacity, 0,
- sizeof(Entry) * (type->entry_count - type->entry_capacity));
+ if (last_package_idx != package_idx) {
+ std::unique_ptr<Package>& package = packages_[package_idx];
+ if (package == nullptr) {
+ package.reset(new Package());
}
- type->entry_capacity = type->entry_count;
+ last_package_idx = package_idx;
+ last_package = package.get();
+ last_type_idx = -1;
}
- Entry& entry = type->entries[entry_idx];
- if (force || entry.value.dataType == Res_value::TYPE_NULL) {
+
+ if (last_type_idx != type_idx) {
+ util::unique_cptr<ThemeType>& type = last_package->types[type_idx];
+ if (type == nullptr) {
+ // Allocate enough memory to contain this entry_idx. Since we're iterating in reverse over
+ // a sorted list of attributes, this shouldn't be resized again during this method call.
+ type.reset(reinterpret_cast<ThemeType*>(
+ calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1)));
+ type->entry_count = entry_idx + 1;
+ } else if (entry_idx >= type->entry_count) {
+ // Reallocate the memory to contain this entry_idx. Since we're iterating in reverse over
+ // a sorted list of attributes, this shouldn't be resized again during this method call.
+ const int new_count = entry_idx + 1;
+ type.reset(reinterpret_cast<ThemeType*>(
+ realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry)))));
+
+ // Clear out the newly allocated space (which isn't zeroed).
+ memset(type->entries + type->entry_count, 0,
+ (new_count - type->entry_count) * sizeof(ThemeEntry));
+ type->entry_count = new_count;
+ }
+ last_type_idx = type_idx;
+ last_type = type.get();
+ }
+
+ ThemeEntry& entry = last_type->entries[entry_idx];
+ if (force || (entry.value.dataType == Res_value::TYPE_NULL &&
+ entry.value.data != Res_value::DATA_NULL_EMPTY)) {
entry.cookie = bag_iter->cookie;
entry.type_spec_flags |= bag->type_spec_flags;
entry.value = bag_iter->value;
@@ -789,88 +828,46 @@
ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
uint32_t* out_flags) const {
- constexpr const int kMaxIterations = 20;
+ int cnt = 20;
uint32_t type_spec_flags = 0u;
- for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) {
- if (!is_valid_resid(resid)) {
- return kInvalidCookie;
- }
-
- const uint32_t package_idx = get_package_id(resid);
-
- // Type ID is 1-based, subtract 1 to get the index.
- const uint32_t type_idx = get_type_id(resid) - 1;
- const uint32_t entry_idx = get_entry_id(resid);
-
+ do {
+ const int package_idx = get_package_id(resid);
const Package* package = packages_[package_idx].get();
- if (package == nullptr) {
- return kInvalidCookie;
- }
+ if (package != nullptr) {
+ // The themes are constructed with a 1-based type ID, so no need to decrement here.
+ const int type_idx = get_type_id(resid);
+ const ThemeType* type = package->types[type_idx].get();
+ if (type != nullptr) {
+ const int entry_idx = get_entry_id(resid);
+ if (entry_idx < type->entry_count) {
+ const ThemeEntry& entry = type->entries[entry_idx];
+ type_spec_flags |= entry.type_spec_flags;
- const Type* type = package->types[type_idx].get();
- if (type == nullptr) {
- return kInvalidCookie;
- }
+ if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) {
+ if (cnt > 0) {
+ cnt--;
+ resid = entry.value.data;
+ continue;
+ }
+ return kInvalidCookie;
+ }
- if (entry_idx >= type->entry_count) {
- return kInvalidCookie;
- }
+ // @null is different than @empty.
+ if (entry.value.dataType == Res_value::TYPE_NULL &&
+ entry.value.data != Res_value::DATA_NULL_EMPTY) {
+ return kInvalidCookie;
+ }
- const Entry& entry = type->entries[entry_idx];
- type_spec_flags |= entry.type_spec_flags;
-
- switch (entry.value.dataType) {
- case Res_value::TYPE_NULL:
- return kInvalidCookie;
-
- case Res_value::TYPE_ATTRIBUTE:
- resid = entry.value.data;
- break;
-
- case Res_value::TYPE_DYNAMIC_ATTRIBUTE: {
- // Resolve the dynamic attribute to a normal attribute
- // (with the right package ID).
- resid = entry.value.data;
- const DynamicRefTable* ref_table =
- asset_manager_->GetDynamicRefTableForPackage(package_idx);
- if (ref_table == nullptr || ref_table->lookupResourceId(&resid) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic attribute 0x%08x", resid);
- return kInvalidCookie;
- }
- } break;
-
- case Res_value::TYPE_DYNAMIC_REFERENCE: {
- // Resolve the dynamic reference to a normal reference
- // (with the right package ID).
- out_value->dataType = Res_value::TYPE_REFERENCE;
- out_value->data = entry.value.data;
- const DynamicRefTable* ref_table =
- asset_manager_->GetDynamicRefTableForPackage(package_idx);
- if (ref_table == nullptr || ref_table->lookupResourceId(&out_value->data) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic reference 0x%08x",
- out_value->data);
- return kInvalidCookie;
- }
-
- if (out_flags != nullptr) {
+ *out_value = entry.value;
*out_flags = type_spec_flags;
+ return entry.cookie;
}
- return entry.cookie;
}
-
- default:
- *out_value = entry.value;
- if (out_flags != nullptr) {
- *out_flags = type_spec_flags;
- }
- return entry.cookie;
}
- }
-
- LOG(WARNING) << base::StringPrintf("Too many (%d) attribute references, stopped at: 0x%08x",
- kMaxIterations, resid);
+ break;
+ } while (true);
return kInvalidCookie;
}
@@ -923,7 +920,7 @@
}
for (size_t t = 0; t < package->types.size(); t++) {
- const Type* type = package->types[t].get();
+ const ThemeType* type = package->types[t].get();
if (type == nullptr) {
// The other theme doesn't have this type, clear ours.
packages_[p]->types[t].reset();
@@ -931,10 +928,10 @@
}
// Create a new type and update it to theirs.
- const size_t type_alloc_size = sizeof(Type) + (type->entry_capacity * sizeof(Entry));
+ const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry));
void* copied_data = malloc(type_alloc_size);
memcpy(copied_data, type, type_alloc_size);
- packages_[p]->types[t].reset(reinterpret_cast<Type*>(copied_data));
+ packages_[p]->types[t].reset(reinterpret_cast<ThemeType*>(copied_data));
}
}
return true;
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index b62fc81..c361ea2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -122,9 +122,180 @@
} // namespace
+LoadedPackage::LoadedPackage() = default;
+LoadedPackage::~LoadedPackage() = default;
+
+// Precondition: The header passed in has already been verified, so reading any fields and trusting
+// the ResChunk_header is safe.
+static bool VerifyResTableType(const ResTable_type* header) {
+ const size_t entry_count = dtohl(header->entryCount);
+ if (entry_count > std::numeric_limits<uint16_t>::max()) {
+ LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
+ return false;
+ }
+
+ // Make sure that there is enough room for the entry offsets.
+ const size_t offsets_offset = dtohs(header->header.headerSize);
+ const size_t entries_offset = dtohl(header->entriesStart);
+ const size_t offsets_length = sizeof(uint32_t) * entry_count;
+
+ if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
+ LOG(ERROR) << "Entry offsets overlap actual entry data.";
+ return false;
+ }
+
+ if (entries_offset > dtohl(header->header.size)) {
+ LOG(ERROR) << "Entry offsets extend beyond chunk.";
+ return false;
+ }
+
+ if (entries_offset & 0x03) {
+ LOG(ERROR) << "Entries start at unaligned address.";
+ return false;
+ }
+ return true;
+}
+
+static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
+ size_t entry_idx) {
+ // Check that the offset is aligned.
+ if (entry_offset & 0x03) {
+ LOG(ERROR) << "Entry offset at index " << entry_idx << " 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.";
+ return false;
+ }
+
+ const size_t chunk_size = dtohl(type->header.size);
+
+ entry_offset += dtohl(type->entriesStart);
+ if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
+ LOG(ERROR) << "Entry offset at index " << entry_idx
+ << " is too large. No room for ResTable_entry.";
+ return false;
+ }
+
+ const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(type) + 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
+ << " 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
+ << " is too large.";
+ return false;
+ }
+
+ 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
+ << " for type " << (int)type->id << ".";
+ return false;
+ }
+
+ const Res_value* value =
+ 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.";
+ 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
+ << " is too large.";
+ return false;
+ }
+ } else {
+ const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
+ 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.";
+ 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 << ".";
+ return false;
+ }
+ }
+ return true;
+}
+
+template <bool Verified>
+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];
+
+ 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->type->entryCount);
+ const size_t offsets_offset = dtohs(type->type->header.headerSize);
+ if (entry_idx < entry_count) {
+ // If the package hasn't been verified, do bounds checking.
+ if (!Verified) {
+ if (!VerifyResTableType(type->type)) {
+ continue;
+ }
+ }
+
+ const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<const uint8_t*>(type->type) + offsets_offset);
+ const uint32_t offset = dtohl(entry_offsets[entry_idx]);
+ if (offset != ResTable_type::NO_ENTRY) {
+ // There is an entry for this resource, record it.
+ best_config = &type->configuration;
+ best_type = type->type;
+ best_offset = offset;
+ }
+ }
+ }
+ }
+
+ if (best_type == nullptr) {
+ return false;
+ }
+
+ if (!Verified) {
+ if (!VerifyResTableEntry(best_type, best_offset, entry_idx)) {
+ return false;
+ }
+ }
+
+ const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
+
+ const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
+ out_entry->type_flags = dtohl(flags[entry_idx]);
+ out_entry->entry = best_entry;
+ out_entry->config = best_config;
+ out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
+ out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
+ return true;
+}
+
bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
- LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
- uint32_t* out_flags) const {
+ FindEntryResult* out_entry) const {
ATRACE_CALL();
// If the type IDs are offset in this package, we need to take that into account when searching
@@ -142,120 +313,27 @@
}
}
- // Don't bother checking if the entry ID is larger than
- // the number of entries.
+ // Don't bother checking if the entry ID is larger than the number of entries.
if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
return false;
}
- const ResTable_config* best_config = nullptr;
- const ResTable_type* best_type = nullptr;
- uint32_t best_offset = 0;
-
- for (uint32_t i = 0; i < ptr->type_count; i++) {
- const Type* type = &ptr->types[i];
-
- 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.
- size_t entry_count = dtohl(type->type->entryCount);
- if (entry_idx < entry_count) {
- const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
- reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
- const uint32_t offset = dtohl(entry_offsets[entry_idx]);
- if (offset != ResTable_type::NO_ENTRY) {
- // There is an entry for this resource, record it.
- best_config = &type->configuration;
- best_type = type->type;
- best_offset = offset + dtohl(type->type->entriesStart);
- }
- }
- }
+ if (verified_) {
+ return FindEntry<true>(ptr, entry_idx, config, out_entry);
}
-
- if (best_type == nullptr) {
- return false;
- }
-
- const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1);
- *out_flags = dtohl(flags[entry_idx]);
- *out_selected_config = *best_config;
-
- const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
- reinterpret_cast<const uint8_t*>(best_type) + best_offset);
- out_entry->entry = best_entry;
- 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;
-}
-
-// The destructor gets generated into arbitrary translation units
-// if left implicit, which causes the compiler to complain about
-// forward declarations and incomplete types.
-LoadedArsc::~LoadedArsc() {}
-
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
- LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
- uint32_t* out_flags) 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 (type_id == 0) {
- LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << ".";
- return false;
- }
-
- for (const auto& loaded_package : packages_) {
- if (loaded_package->package_id_ == package_id) {
- return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry,
- out_selected_config, out_flags);
- }
- }
- return false;
-}
-
-const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
- const uint8_t package_id = get_package_id(resid);
- for (const auto& loaded_package : packages_) {
- if (loaded_package->package_id_ == package_id) {
- return loaded_package.get();
- }
- }
- return nullptr;
+ return FindEntry<false>(ptr, entry_idx, config, out_entry);
}
static bool VerifyType(const Chunk& chunk) {
ATRACE_CALL();
const ResTable_type* header = chunk.header<ResTable_type, kResTableTypeMinSize>();
+ if (!VerifyResTableType(header)) {
+ return false;
+ }
+
const size_t entry_count = dtohl(header->entryCount);
- if (entry_count > std::numeric_limits<uint16_t>::max()) {
- LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
- return false;
- }
-
- // Make sure that there is enough room for the entry offsets.
const size_t offsets_offset = chunk.header_size();
- const size_t entries_offset = dtohl(header->entriesStart);
- const size_t offsets_length = sizeof(uint32_t) * entry_count;
-
- if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
- LOG(ERROR) << "Entry offsets overlap actual entry data.";
- return false;
- }
-
- if (entries_offset > chunk.size()) {
- LOG(ERROR) << "Entry offsets extend beyond chunk.";
- return false;
- }
-
- if (entries_offset & 0x03) {
- LOG(ERROR) << "Entries start at unaligned address.";
- return false;
- }
// Check each entry offset.
const uint32_t* offsets =
@@ -263,79 +341,9 @@
for (size_t i = 0; i < entry_count; i++) {
uint32_t offset = dtohl(offsets[i]);
if (offset != ResTable_type::NO_ENTRY) {
- // Check that the offset is aligned.
- if (offset & 0x03) {
- LOG(ERROR) << "Entry offset at index " << i << " is not 4-byte aligned.";
+ if (!VerifyResTableEntry(header, offset, i)) {
return false;
}
-
- // Check that the offset doesn't overflow.
- if (offset > std::numeric_limits<uint32_t>::max() - entries_offset) {
- // Overflow in offset.
- LOG(ERROR) << "Entry offset at index " << i << " is too large.";
- return false;
- }
-
- offset += entries_offset;
- if (offset > chunk.size() - sizeof(ResTable_entry)) {
- LOG(ERROR) << "Entry offset at index " << i << " is too large. No room for ResTable_entry.";
- return false;
- }
-
- const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
- reinterpret_cast<const uint8_t*>(header) + offset);
- const size_t entry_size = dtohs(entry->size);
- if (entry_size < sizeof(*entry)) {
- LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too small.";
- return false;
- }
-
- // Check the declared entrySize.
- if (entry_size > chunk.size() || offset > chunk.size() - entry_size) {
- LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too large.";
- return false;
- }
-
- // If this is a map entry, then keep validating.
- if (entry_size >= sizeof(ResTable_map_entry)) {
- const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
- const size_t map_entry_count = dtohl(map->count);
-
- size_t map_entries_start = offset + entry_size;
- if (map_entries_start & 0x03) {
- LOG(ERROR) << "Map entries at index " << i << " 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 " << i << ".";
- return false;
- }
-
- // Great, all the map entries fit!.
- } else {
- // There needs to be room for one Res_value struct.
- if (offset + entry_size > chunk.size() - sizeof(Res_value)) {
- LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << i << " for type "
- << (int)header->id << " with config " << header->config.toString().string()
- << ".";
- return false;
- }
-
- const Res_value* value = 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 " << i << " is too small.";
- return false;
- }
-
- if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) {
- LOG(ERROR) << "Res_value size " << value_size << " at index " << i << " is too large.";
- return false;
- }
- }
}
}
return true;
@@ -431,10 +439,21 @@
return 0u;
}
-std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
- const LoadedIdmap* loaded_idmap) {
+const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
+ const uint8_t package_id = get_package_id(resid);
+ for (const auto& loaded_package : packages_) {
+ if (loaded_package->GetPackageId() == package_id) {
+ return loaded_package.get();
+ }
+ }
+ return nullptr;
+}
+
+std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
+ const LoadedIdmap* loaded_idmap,
+ bool system, bool load_as_shared_library) {
ATRACE_CALL();
- std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
+ std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
// typeIdOffset was added at some point, but we still must recognize apps built before this
// was added.
@@ -446,8 +465,11 @@
return {};
}
+ loaded_package->system_ = system;
+
loaded_package->package_id_ = dtohl(header->id);
- if (loaded_package->package_id_ == 0) {
+ if (loaded_package->package_id_ == 0 ||
+ (loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {
// Package ID of 0 means this is a shared library.
loaded_package->dynamic_ = true;
}
@@ -593,13 +615,16 @@
// Type chunks must be preceded by their TypeSpec chunks.
if (!types_builder || type->id - 1 != last_type_idx) {
- LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without "
- "RES_TABLE_TYPE_SPEC_TYPE.";
+ LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without RES_TABLE_TYPE_SPEC_TYPE.";
return {};
}
- if (!VerifyType(child_chunk)) {
- return {};
+ // Only verify the type if we haven't already failed verification.
+ if (loaded_package->verified_) {
+ if (!VerifyType(child_chunk)) {
+ LOG(WARNING) << "Package failed verification, resource retrieval may be slower";
+ loaded_package->verified_ = false;
+ }
}
types_builder->AddType(type);
@@ -669,7 +694,28 @@
LOG(ERROR) << iter.GetLastError();
return {};
}
- return loaded_package;
+ 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 (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,
@@ -712,17 +758,11 @@
}
packages_seen++;
- std::unique_ptr<LoadedPackage> loaded_package =
- LoadedPackage::Load(child_chunk, loaded_idmap);
+ std::unique_ptr<const LoadedPackage> loaded_package =
+ LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
if (!loaded_package) {
return false;
}
-
- // Mark the package as dynamic if we are forcefully loading the Apk as a shared library.
- if (loaded_package->package_id_ == kAppPackageId) {
- loaded_package->dynamic_ = load_as_shared_library;
- }
- loaded_package->system_ = system_;
packages_.push_back(std::move(loaded_package));
} break;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 7a0ef2b..87999c3 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6014,9 +6014,6 @@
StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index)
: mPool(pool), mIndex(index) {}
-StringPoolRef::StringPoolRef()
- : mPool(NULL), mIndex(0) {}
-
const char* StringPoolRef::string8(size_t* outLen) const {
if (mPool != NULL) {
return mPool->string8At(mIndex, outLen);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b29bc3a..a77c4b9 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -245,21 +245,22 @@
private:
DISALLOW_COPY_AND_ASSIGN(AssetManager2);
- // Finds the best entry for `resid` amongst all the ApkAssets. The entry can be a simple
- // Res_value, or a complex map/bag type.
+ // Finds the best entry for `resid` from the set of ApkAssets. The entry can be a simple
+ // Res_value, or a complex map/bag type. If successful, it is available in `out_entry`.
+ // Returns kInvalidCookie on failure. Otherwise, the return value is the cookie associated with
+ // the ApkAssets in which the entry was found.
//
// `density_override` overrides the density of the current configuration when doing a search.
//
// When `stop_at_first_match` is true, the first match found is selected and the search
// terminates. This is useful for methods that just look up the name of a resource and don't
- // care about the value. In this case, the value of `out_flags` is incomplete and should not
- // be used.
+ // care about the value. In this case, the value of `FindEntryResult::type_flags` is incomplete
+ // and should not be used.
//
- // `out_flags` stores the resulting bitmask of configuration axis with which the resource
- // value varies.
+ // 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,
- LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
- uint32_t* out_flags);
+ FindEntryResult* out_entry);
// Assigns package IDs to all shared library ApkAssets.
// Should be called whenever the ApkAssets are changed.
@@ -302,6 +303,8 @@
friend class AssetManager2;
public:
+ ~Theme();
+
// Applies the style identified by `resid` to this theme. This can be called
// multiple times with different styles. By default, any theme attributes that
// are already defined before this call are not overridden. If `force` is set
@@ -316,27 +319,31 @@
void Clear();
- inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
+ inline const AssetManager2* GetAssetManager() const {
+ return asset_manager_;
+ }
- inline AssetManager2* GetAssetManager() { return asset_manager_; }
+ inline AssetManager2* GetAssetManager() {
+ return asset_manager_;
+ }
// Returns a bit mask of configuration changes that will impact this
// theme (and thus require completely reloading it).
- inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
+ inline uint32_t GetChangingConfigurations() const {
+ return type_spec_flags_;
+ }
- // Retrieve a value in the theme. If the theme defines this value,
- // returns an asset cookie indicating which ApkAssets it came from
- // and populates `out_value` with the value. If `out_flags` is non-null,
- // populates it with a bitmask of the configuration axis the resource
- // varies with.
+ // Retrieve a value in the theme. If the theme defines this value, returns an asset cookie
+ // indicating which ApkAssets it came from and populates `out_value` with the value.
+ // `out_flags` is populated with a bitmask of the configuration axis with which the resource
+ // varies.
//
// If the attribute is not found, returns kInvalidCookie.
//
- // NOTE: This function does not do reference traversal. If you want
- // to follow references to other resources to get the "real" value to
- // use, you need to call ResolveReference() after this function.
- ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value,
- uint32_t* out_flags = nullptr) const;
+ // NOTE: This function does not do reference traversal. If you want to follow references to other
+ // resources to get the "real" value to use, you need to call ResolveReference() after this
+ // function.
+ ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value, uint32_t* out_flags) const;
// This is like AssetManager2::ResolveReference(), but also takes
// care of resolving attribute references to the theme.
@@ -349,36 +356,21 @@
DISALLOW_COPY_AND_ASSIGN(Theme);
// Called by AssetManager2.
- explicit inline Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {}
-
- struct Entry {
- ApkAssetsCookie cookie;
- uint32_t type_spec_flags;
- Res_value value;
- };
-
- struct Type {
- // Use uint32_t for fewer cycles when loading from memory.
- uint32_t entry_count;
- uint32_t entry_capacity;
- Entry entries[0];
- };
-
- static constexpr const size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
- static constexpr const size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
-
- struct Package {
- // Each element of Type will be a dynamically sized object
- // allocated to have the entries stored contiguously with the Type.
- std::array<util::unique_cptr<Type>, kTypeCount> types;
- };
+ explicit Theme(AssetManager2* asset_manager);
AssetManager2* asset_manager_;
uint32_t type_spec_flags_ = 0u;
+
+ // Defined in the cpp.
+ struct Package;
+
+ constexpr static size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
std::array<std::unique_ptr<Package>, kPackageCount> packages_;
};
-inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; }
+inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) {
+ return bag->entries;
+}
inline const ResolvedBag::Entry* end(const ResolvedBag* bag) {
return bag->entries + bag->entry_count;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 1f272e8..377735b 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -41,12 +41,18 @@
int package_id = 0;
};
-struct LoadedArscEntry {
+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;
@@ -63,12 +69,22 @@
class LoadedArsc;
class LoadedPackage {
- friend class LoadedArsc;
-
public:
+ static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
+ const LoadedIdmap* loaded_idmap, bool system,
+ bool load_as_shared_library);
+
+ ~LoadedPackage();
+
bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
- LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
- uint32_t* out_flags) const;
+ 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.
+ // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
+ // for patching the correct package ID to the resource ID.
+ uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
// Returns the string pool where type names are stored.
inline const ResStringPool* GetTypeStringPool() const {
@@ -98,10 +114,16 @@
return system_;
}
+ // Returns true if this package is from an overlay ApkAssets.
inline bool IsOverlay() const {
return overlay_;
}
+ // Returns true if this package is verified to be valid.
+ inline bool IsVerified() const {
+ return verified_;
+ }
+
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -118,19 +140,14 @@
// 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;
- // 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.
- // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
- // for patching the correct package ID to the resource ID.
- uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
-
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
- static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk, const LoadedIdmap* loaded_idmap);
+ LoadedPackage();
- LoadedPackage() = default;
+ template <bool Verified>
+ 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_;
@@ -140,6 +157,7 @@
bool dynamic_ = false;
bool system_ = false;
bool overlay_ = false;
+ bool verified_ = true;
ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
@@ -163,8 +181,6 @@
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<const LoadedArsc> CreateEmpty();
- ~LoadedArsc();
-
// Returns the string pool where all string resource values
// (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
inline const ResStringPool* GetStringPool() const {
@@ -175,8 +191,7 @@
// 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, LoadedArscEntry* out_entry,
- ResTable_config* selected_config, uint32_t* out_flags) const;
+ bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const;
// Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
const LoadedPackage* GetPackageForId(uint32_t resid) const;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 8f858b6..8547955 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -546,15 +546,15 @@
*/
class StringPoolRef {
public:
- StringPoolRef();
- StringPoolRef(const ResStringPool* pool, uint32_t index);
+ StringPoolRef() = default;
+ StringPoolRef(const ResStringPool* pool, uint32_t index);
- const char* string8(size_t* outLen) const;
- const char16_t* string16(size_t* outLen) const;
+ const char* string8(size_t* outLen) const;
+ const char16_t* string16(size_t* outLen) const;
private:
- const ResStringPool* mPool;
- uint32_t mIndex;
+ const ResStringPool* mPool = nullptr;
+ uint32_t mIndex = 0u;
};
/** ********************************************************************
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index 6bf7c24..c2eae85 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -40,7 +40,9 @@
return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
}
-inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
+inline uint16_t get_entry_id(uint32_t resid) {
+ return static_cast<uint16_t>(resid & 0x0000ffffu);
+}
inline bool is_internal_resid(uint32_t resid) {
return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 2766ce1..d65d93f 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -32,7 +32,13 @@
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
ASSERT_NE(nullptr, loaded_apk);
- EXPECT_NE(nullptr, loaded_apk->GetLoadedArsc());
+
+ 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);
+ EXPECT_TRUE(loaded_package->IsVerified());
std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
ASSERT_NE(nullptr, asset);
@@ -87,6 +93,20 @@
ASSERT_NE(nullptr, loaded_overlay_apk);
}
+TEST(ApkAssetsTest, LoadUnverifiableApk) {
+ std::unique_ptr<const ApkAssets> loaded_apk =
+ ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+ ASSERT_NE(nullptr, loaded_apk);
+
+ 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);
+
+ EXPECT_FALSE(loaded_package->IsVerified());
+}
+
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 99a07a5..739e733 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -26,10 +26,12 @@
#include "data/basic/R.h"
#include "data/libclient/R.h"
#include "data/styles/R.h"
+#include "data/unverified/R.h"
namespace app = com::android::app;
namespace basic = com::android::basic;
namespace libclient = com::android::libclient;
+namespace unverified = com::android::unverified;
namespace android {
@@ -124,6 +126,12 @@
}
BENCHMARK(BM_AssetManagerGetResourceOld);
+static void BM_AssetManagerGetResourceUnverified(benchmark::State& state) {
+ GetResourceBenchmark({GetTestDataPath() + "/unverified/unverified.apk"}, nullptr /*config*/,
+ unverified::R::integer::number1, state);
+}
+BENCHMARK(BM_AssetManagerGetResourceUnverified);
+
static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
GetResourceBenchmark(
{GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk",
@@ -206,6 +214,30 @@
}
BENCHMARK(BM_AssetManagerGetBagOld);
+static void BM_AssetManagerGetBagUnverified(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> apk =
+ ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ while (state.KeepRunning()) {
+ const ResolvedBag* bag = assets.GetBag(unverified::R::array::integerArray1);
+ const auto bag_end = end(bag);
+ for (auto iter = begin(bag); iter != bag_end; ++iter) {
+ uint32_t key = iter->key;
+ Res_value value = iter->value;
+ benchmark::DoNotOptimize(key);
+ benchmark::DoNotOptimize(value);
+ }
+ }
+}
+BENCHMARK(BM_AssetManagerGetBagUnverified);
+
static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
if (apk == nullptr) {
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index fcae53b..ab1a22e 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -28,6 +28,7 @@
#include "data/libclient/R.h"
#include "data/styles/R.h"
#include "data/system/R.h"
+#include "data/unverified/R.h"
namespace app = com::android::app;
namespace appaslib = com::android::appaslib::app;
@@ -35,6 +36,7 @@
namespace lib_one = com::android::lib_one;
namespace lib_two = com::android::lib_two;
namespace libclient = com::android::libclient;
+namespace unverified = com::android::unverified;
namespace android {
@@ -431,4 +433,30 @@
TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
+TEST_F(AssetManager2Test, OperateOnUnverifiedApkAssets) {
+ std::unique_ptr<const ApkAssets> unverified_assets =
+ ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+ ASSERT_NE(nullptr, unverified_assets);
+
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({unverified_assets.get()});
+
+ Res_value value;
+ ResTable_config config;
+ uint32_t flags;
+
+ EXPECT_EQ(kInvalidCookie,
+ assetmanager.GetResource(unverified::R::string::test1, false /*may_be_bag*/, 0u, &value,
+ &config, &flags));
+ EXPECT_EQ(kInvalidCookie,
+ assetmanager.GetResource(unverified::R::string::test2, false /*may_be_bag*/, 0u, &value,
+ &config, &flags));
+ EXPECT_NE(kInvalidCookie,
+ assetmanager.GetResource(unverified::R::integer::number1, false /*may_be_bag*/, 0u,
+ &value, &config, &flags));
+
+ EXPECT_EQ(nullptr, assetmanager.GetBag(unverified::R::style::Theme1));
+ EXPECT_NE(nullptr, assetmanager.GetBag(unverified::R::array::integerArray1));
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 2b72d14..954a54d 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -44,12 +44,9 @@
memset(&config, 0, sizeof(config));
config.sdkVersion = 24;
- LoadedArscEntry entry;
- ResTable_config selected_config;
- uint32_t flags;
+ FindEntryResult entry;
- ASSERT_TRUE(
- loaded_arsc->FindEntry(app::R::string::string_one, config, &entry, &selected_config, &flags));
+ ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
ASSERT_NE(nullptr, entry.entry);
}
@@ -66,12 +63,8 @@
desired_config.language[0] = 'd';
desired_config.language[1] = 'e';
- LoadedArscEntry entry;
- ResTable_config selected_config;
- uint32_t flags;
-
- ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry,
- &selected_config, &flags));
+ FindEntryResult entry;
+ ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
ASSERT_NE(nullptr, entry.entry);
}
@@ -150,23 +143,15 @@
ResTable_config desired_config;
memset(&desired_config, 0, sizeof(desired_config));
- LoadedArscEntry entry;
- ResTable_config selected_config;
- uint32_t flags;
-
- ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry,
- &selected_config, &flags));
+ FindEntryResult entry;
+ ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
size_t len;
const char16_t* type_name16 = entry.type_string_ref.string16(&len);
ASSERT_NE(nullptr, type_name16);
ASSERT_NE(0u, len);
- size_t utf8_len = utf16_to_utf8_length(type_name16, len);
- std::string type_name;
- type_name.resize(utf8_len);
- utf16_to_utf8(type_name16, len, &*type_name.begin(), utf8_len + 1);
-
+ std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
EXPECT_EQ(std::string("string"), type_name);
}
@@ -210,12 +195,8 @@
ResTable_config desired_config;
memset(&desired_config, 0, sizeof(desired_config));
- LoadedArscEntry entry;
- ResTable_config selected_config;
- uint32_t flags;
-
- ASSERT_TRUE(
- loaded_arsc->FindEntry(0x08030001u, desired_config, &entry, &selected_config, &flags));
+ FindEntryResult entry;
+ ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
}
// structs with size fields (like Res_value, ResTable_entry) should be
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index feb454e..55d53ed 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -128,6 +128,18 @@
EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
}
+TEST_F(ThemeTest, TryToUseBadResourceId) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+
+ Res_value value;
+ uint32_t flags;
+ ASSERT_EQ(kInvalidCookie, theme->GetAttribute(0x7f000001, &value, &flags));
+}
+
TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({style_assets_.get()});
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/libs/androidfw/tests/data/unverified/R.h
similarity index 62%
copy from core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
copy to libs/androidfw/tests/data/unverified/R.h
index 7294124..b734b49 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ b/libs/androidfw/tests/data/unverified/R.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,19 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+#ifndef TESTS_DATA_UNVERIFIED_R_H_
+#define TESTS_DATA_UNVERIFIED_R_H_
-import android.os.IBinder;
+#include <cstdint>
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
-}
+#include "tests/data/basic/R.h"
+
+namespace com {
+namespace android {
+
+namespace unverified = basic;
+
+} // namespace android
+} // namespace com
+
+#endif /* TESTS_DATA_UNVERIFIED_R_H_ */
diff --git a/libs/androidfw/tests/data/unverified/unverified.apk b/libs/androidfw/tests/data/unverified/unverified.apk
new file mode 100644
index 0000000..234b390
--- /dev/null
+++ b/libs/androidfw/tests/data/unverified/unverified.apk
Binary files differ
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 1e71cb0..6b8006c 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -31,6 +31,12 @@
namespace uirenderer {
Extensions::Extensions() {
+ if (Properties::getRenderPipelineType() != RenderPipelineType::OpenGL) {
+ //Extensions class is used only by OpenGL pipeline
+ //The code below will crash for SkiaVulkan, because OpenGL is not initialized
+ //TODO: instantiate Extensions class only for OpenGL pipeline
+ return;
+ }
const char* version = (const char*) glGetString(GL_VERSION);
// Section 6.1.5 of the OpenGL ES specification indicates the GL version
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 3b59bb1..588c4de 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -58,6 +58,7 @@
bool Properties::filterOutTestOverhead = false;
bool Properties::disableVsync = false;
+bool Properties::skpCaptureEnabled = false;
static int property_get_int(const char* key, int defaultValue) {
char buf[PROPERTY_VALUE_MAX] = {'\0',};
@@ -128,6 +129,9 @@
filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
+ skpCaptureEnabled = property_get_bool("ro.debuggable", false)
+ && property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false);
+
return (prevDebugLayersUpdates != debugLayersUpdates)
|| (prevDebugOverdraw != debugOverdraw)
|| (prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 0fe4c77..077e7ae 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -165,6 +165,22 @@
*/
#define PROPERTY_RENDERER "debug.hwui.renderer"
+/**
+ * Allows to collect a recording of Skia drawing commands.
+ */
+#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
+
+
+/**
+ * Defines how many frames in a sequence to capture.
+ */
+#define PROPERTY_CAPTURE_SKP_FRAMES "debug.hwui.capture_skp_frames"
+
+/**
+ * File name and location, where a SKP recording will be saved.
+ */
+#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -254,6 +270,8 @@
// created after changing this.
static bool disableVsync;
+ static bool skpCaptureEnabled;
+
// Used for testing only to change the render pipeline.
#ifdef HWUI_GLES_WRAP_ENABLED
static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3b72a8b..def04e1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -35,6 +35,10 @@
}
bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer) {
+ if (context == nullptr) {
+ SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+ return false;
+ }
// transform the matrix based on the layer
SkMatrix layerTransform;
layer->getTransform().copyTo(layerTransform);
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 47dee9d..ca93925 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -101,7 +101,7 @@
void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
RenderNode* renderNode = mRenderNode.get();
- if (SkiaPipeline::skpCaptureEnabled()) {
+ if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index 9982a0c..75967e9 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -73,7 +73,7 @@
* for reading back float buffers (skbug.com/6945).
*/
if (pixelConfig == kRGBA_half_GrPixelConfig &&
- !DeviceInfo::get()->extensions().hasFloatTextures()) {
+ !grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
return CopyResult::DestinationInvalid;
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index c4bd1e1..a092cd5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -75,6 +75,12 @@
mPinnedImages.clear();
}
+void SkiaPipeline::onPrepareTree() {
+ // The only time mVectorDrawables is not empty is if prepare tree was called 2 times without
+ // a renderFrame in the middle.
+ mVectorDrawables.clear();
+}
+
void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
const BakedOpRenderer::LightInfo& lightInfo) {
@@ -106,7 +112,7 @@
const Rect& layerDamage = layers.entries()[i].damage;
- SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+ SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
int saveCount = layerCanvas->save();
SkASSERT(saveCount == 1);
@@ -133,9 +139,11 @@
layerCanvas->restoreToCount(saveCount);
mLightCenter = savedLightCenter;
+ endCapture(layerNode->getLayerSurface());
+
// cache the current context so that we can defer flushing it until
// either all the layers have been rendered or the context changes
- GrContext* currentContext = layerCanvas->getGrContext();
+ GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
if (cachedContext.get() != currentContext) {
if (cachedContext.get()) {
cachedContext->flush();
@@ -221,6 +229,91 @@
}
}
+class SkiaPipeline::SavePictureProcessor : public TaskProcessor<bool> {
+public:
+ explicit SavePictureProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {}
+
+ struct SavePictureTask : public Task<bool> {
+ sk_sp<SkData> data;
+ std::string filename;
+ };
+
+ void savePicture(const sk_sp<SkData>& data, const std::string& filename) {
+ sp<SavePictureTask> task(new SavePictureTask());
+ task->data = data;
+ task->filename = filename;
+ TaskProcessor<bool>::add(task);
+ }
+
+ virtual void onProcess(const sp<Task<bool> >& task) override {
+ SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
+
+ if (0 == access(t->filename.c_str(), F_OK)) {
+ task->setResult(false);
+ return;
+ }
+
+ SkFILEWStream stream(t->filename.c_str());
+ if (stream.isValid()) {
+ stream.write(t->data->data(), t->data->size());
+ stream.flush();
+ SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s",
+ stream.bytesWritten(), t->filename.c_str());
+ }
+
+ task->setResult(true);
+ }
+};
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+ if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+ bool recordingPicture = mCaptureSequence > 0;
+ char prop[PROPERTY_VALUE_MAX] = {'\0'};
+ if (!recordingPicture) {
+ property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
+ recordingPicture = prop[0] != '0'
+ && mCapturedFile != prop; //ensure we capture only once per filename
+ if (recordingPicture) {
+ mCapturedFile = prop;
+ mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
+ }
+ }
+ if (recordingPicture) {
+ mRecorder.reset(new SkPictureRecorder());
+ return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+ SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+ }
+ }
+ return surface->getCanvas();
+}
+
+void SkiaPipeline::endCapture(SkSurface* surface) {
+ if (CC_UNLIKELY(mRecorder.get())) {
+ sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
+ surface->getCanvas()->drawPicture(picture);
+ if (picture->approximateOpCount() > 0) {
+ SkDynamicMemoryWStream stream;
+ PngPixelSerializer serializer;
+ picture->serialize(&stream, &serializer);
+
+ //offload saving to file in a different thread
+ if (!mSavePictureProcessor.get()) {
+ TaskManager* taskManager = getTaskManager();
+ mSavePictureProcessor = new SavePictureProcessor(
+ taskManager->canRunTasks() ? taskManager : nullptr);
+ }
+ if (1 == mCaptureSequence) {
+ mSavePictureProcessor->savePicture(stream.detachAsData(), mCapturedFile);
+ } else {
+ mSavePictureProcessor->savePicture(stream.detachAsData(),
+ mCapturedFile + "_" + std::to_string(mCaptureSequence));
+ }
+ mCaptureSequence--;
+ }
+ mRecorder.reset();
+ }
+}
+
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
const Rect &contentDrawBounds, sk_sp<SkSurface> surface) {
@@ -230,44 +323,21 @@
// draw all layers up front
renderLayersImpl(layers, opaque, wideColorGamut);
- // initialize the canvas for the current frame
- SkCanvas* canvas = surface->getCanvas();
-
+ // initialize the canvas for the current frame, that might be a recording canvas if SKP
+ // capture is enabled.
std::unique_ptr<SkPictureRecorder> recorder;
- bool recordingPicture = false;
- char prop[PROPERTY_VALUE_MAX];
- if (skpCaptureEnabled()) {
- property_get("debug.hwui.capture_frame_as_skp", prop, "0");
- recordingPicture = prop[0] != '0' && access(prop, F_OK) != 0;
- if (recordingPicture) {
- recorder.reset(new SkPictureRecorder());
- canvas = recorder->beginRecording(surface->width(), surface->height(),
- nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
- }
- }
+ SkCanvas* canvas = tryCapture(surface.get());
renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);
- if (skpCaptureEnabled() && recordingPicture) {
- sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
- if (picture->approximateOpCount() > 0) {
- SkFILEWStream stream(prop);
- if (stream.isValid()) {
- PngPixelSerializer serializer;
- picture->serialize(&stream, &serializer);
- stream.flush();
- SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop);
- }
- }
- surface->getCanvas()->drawPicture(picture);
- }
+ endCapture(surface.get());
if (CC_UNLIKELY(Properties::debugOverdraw)) {
renderOverdraw(layers, clip, nodes, contentDrawBounds, surface);
}
ATRACE_NAME("flush commands");
- canvas->flush();
+ surface->getCanvas()->flush();
}
namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 3e6ae30..67b1878 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -21,6 +21,8 @@
#include "renderthread/IRenderPipeline.h"
#include <SkSurface.h>
+class SkPictureRecorder;
+
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -37,6 +39,7 @@
bool pinImages(std::vector<SkImage*>& mutableImages) override;
bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
void unpinImages() override;
+ void onPrepareTree() override;
void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
@@ -55,9 +58,7 @@
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
- static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
-
- static bool skpCaptureEnabled() { return false; }
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
static float getLightRadius() {
if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) {
@@ -126,12 +127,38 @@
*/
void renderVectorDrawableCache();
+ SkCanvas* tryCapture(SkSurface* surface);
+ void endCapture(SkSurface* surface);
+
std::vector<sk_sp<SkImage>> mPinnedImages;
/**
* populated by prepareTree with dirty VDs
*/
std::vector<VectorDrawableRoot*> mVectorDrawables;
+
+
+ // Block of properties used only for debugging to record a SkPicture and save it in a file.
+ /**
+ * mCapturedFile is used to enforce we don't capture more than once for a given name (cause
+ * permissions don't allow to reset a property from render thread).
+ */
+ std::string mCapturedFile;
+ /**
+ * mCaptureSequence counts how many frames are left to take in the sequence.
+ */
+ int mCaptureSequence = 0;
+ /**
+ * mSavePictureProcessor is used to run the file saving code in a separate thread.
+ */
+ class SavePictureProcessor;
+ sp<SavePictureProcessor> mSavePictureProcessor;
+ /**
+ * mRecorder holds the current picture recorder. We could store it on the stack to support
+ * parallel tryCapture calls (not really needed).
+ */
+ std::unique_ptr<SkPictureRecorder> mRecorder;
+
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;
static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5d7f594..ad684db 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -331,6 +331,7 @@
info.layerUpdateQueue = &mLayerUpdateQueue;
mAnimationContext->startFrame(info.mode);
+ mRenderPipeline->onPrepareTree();
for (const sp<RenderNode>& node : mRenderNodes) {
// Only the primary target node will be drawn full - all other nodes would get drawn in
// real time mode. In case of a window, the primary node is the window content and the other
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index f9b6e38..0bb3889 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -81,6 +81,7 @@
virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
virtual void unpinImages() = 0;
+ virtual void onPrepareTree() = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 4ca19fb..1f467c1 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -58,6 +58,7 @@
bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override;
void unpinImages() override;
+ void onPrepareTree() override {}
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap);
static void invokeFunctor(const RenderThread& thread, Functor* functor);
diff --git a/libs/hwui/tests/scripts/skp-capture.sh b/libs/hwui/tests/scripts/skp-capture.sh
new file mode 100755
index 0000000..54fa229
--- /dev/null
+++ b/libs/hwui/tests/scripts/skp-capture.sh
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if [ -z "$1" ]; then
+ printf 'Usage:\n skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n'
+ printf "Use \`adb shell 'pm list packages'\` to get a listing.\n\n"
+ exit 1
+fi
+if ! command -v adb > /dev/null 2>&1; then
+ if [ -x "${ANDROID_SDK_ROOT}/platform-tools/adb" ]; then
+ adb() {
+ "${ANDROID_SDK_ROOT}/platform-tools/adb" "$@"
+ }
+ else
+ echo 'adb missing'
+ exit 2
+ fi
+fi
+phase1_timeout_seconds=15
+phase2_timeout_seconds=60
+package="$1"
+filename="$(date '+%H%M%S').skp"
+remote_path="/data/data/${package}/cache/${filename}"
+local_path_prefix="$(date '+%Y-%m-%d_%H%M%S')_${package}"
+local_path="${local_path_prefix}.skp"
+enable_capture_key='debug.hwui.capture_skp_enabled'
+enable_capture_value=$(adb shell "getprop '${enable_capture_key}'")
+#printf 'captureflag=' "$enable_capture_value" '\n'
+if [ -z "$enable_capture_value" ]; then
+ printf 'Capture SKP property need to be enabled first. Please use\n'
+ printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n"
+ printf "the process.\n\n"
+ exit 1
+fi
+if [ ! -z "$2" ]; then
+ adb shell "setprop 'debug.hwui.capture_skp_frames' $2"
+fi
+filename_key='debug.hwui.skp_filename'
+adb shell "setprop '${filename_key}' '${remote_path}'"
+spin() {
+ case "$spin" in
+ 1) printf '\b|';;
+ 2) printf '\b\\';;
+ 3) printf '\b-';;
+ *) printf '\b/';;
+ esac
+ spin=$(( ( ${spin:-0} + 1 ) % 4 ))
+ sleep $1
+}
+
+banner() {
+ printf '\n=====================\n'
+ printf ' %s' "$*"
+ printf '\n=====================\n'
+}
+banner '...WAITING...'
+adb_test_exist() {
+ test '0' = "$(adb shell "test -e \"$1\"; echo \$?")";
+}
+timeout=$(( $(date +%s) + $phase1_timeout_seconds))
+while ! adb_test_exist "$remote_path"; do
+ spin 0.05
+ if [ $(date +%s) -gt $timeout ] ; then
+ printf '\bTimed out.\n'
+ adb shell "setprop '${filename_key}' ''"
+ exit 3
+ fi
+done
+printf '\b'
+
+#read -n1 -r -p "Press any key to continue..." key
+
+banner '...SAVING...'
+adb_test_file_nonzero() {
+ # grab first byte of `du` output
+ X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")"
+ test "$X" && test "$X" -ne 0
+}
+#adb_filesize() {
+# adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
+#}
+timeout=$(( $(date +%s) + $phase2_timeout_seconds))
+while ! adb_test_file_nonzero "$remote_path"; do
+ spin 0.05
+ if [ $(date +%s) -gt $timeout ] ; then
+ printf '\bTimed out.\n'
+ adb shell "setprop '${filename_key}' ''"
+ exit 3
+ fi
+done
+printf '\b'
+
+adb shell "setprop '${filename_key}' ''"
+
+i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo
+
+adb pull "$remote_path" "$local_path"
+if ! [ -f "$local_path" ] ; then
+ printf "something went wrong with `adb pull`."
+ exit 4
+fi
+adb shell rm "$remote_path"
+printf '\nSKP saved to file:\n %s\n\n' "$local_path"
+if [ ! -z "$2" ]; then
+ bridge="_"
+ adb shell "setprop 'debug.hwui.capture_skp_frames' ''"
+ for i in $(seq 2 $2); do
+ adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp"
+ adb shell rm "${remote_path}_${i}"
+ done
+fi
+
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 4c3e182..d182d78 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -22,6 +22,7 @@
#include "IContextFactory.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestUtils.h"
@@ -335,7 +336,7 @@
EXPECT_EQ(3, canvas.getIndex());
}
-RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
/* R is backward projected on B and C is a layer.
A
/ \
@@ -450,7 +451,8 @@
LayerUpdateQueue layerUpdateQueue;
layerUpdateQueue.enqueueLayerWithDamage(child.get(),
android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
- SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false);
+ auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+ pipeline->renderLayersImpl(layerUpdateQueue, true, false);
EXPECT_EQ(1, drawCounter); //assert index 0 is drawn on the layer
RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index b397b15..f430ce6 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -56,6 +56,40 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) {
+
+ auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
+ [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
+ redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+ });
+
+ LayerUpdateQueue layerUpdateQueue;
+ SkRect dirty = SkRect::MakeLargest();
+ std::vector<sp<RenderNode>> renderNodes;
+ renderNodes.push_back(redNode);
+ bool opaque = true;
+ android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
+ auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+ {
+ //add a pointer to a deleted vector drawable object in the pipeline
+ sp<VectorDrawableRoot> dirtyVD(new VectorDrawableRoot(new VectorDrawable::Group()));
+ dirtyVD->mutateProperties()->setScaledSize(5,5);
+ pipeline->getVectorDrawables()->push_back(dirtyVD.get());
+ }
+
+ //pipeline should clean list of dirty vector drawables before prepare tree
+ pipeline->onPrepareTree();
+
+ auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+ //drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer
+ pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+ opaque, false, contentDrawBounds, surface);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+}
+
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2,
[](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
index cbfd4b3..d72059a 100644
--- a/libs/services/Android.mk
+++ b/libs/services/Android.mk
@@ -21,7 +21,8 @@
LOCAL_MODULE := libservices
LOCAL_SRC_FILES := \
../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
- src/os/DropBoxManager.cpp
+ src/os/DropBoxManager.cpp \
+ src/os/StatsLogEventWrapper.cpp
LOCAL_AIDL_INCLUDES := \
$(LOCAL_PATH)/../../core/java
diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h
new file mode 100644
index 0000000..255619c
--- /dev/null
+++ b/libs/services/include/android/os/StatsLogEventWrapper.h
@@ -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.
+ */
+#ifndef STATS_LOG_EVENT_WRAPPER_H
+#define STATS_LOG_EVENT_WRAPPER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+namespace android {
+namespace os {
+
+// Represents a parcelable object. Only used to send data from Android OS to statsd.
+class StatsLogEventWrapper : public android::Parcelable {
+ public:
+ StatsLogEventWrapper();
+
+ StatsLogEventWrapper(StatsLogEventWrapper&& in) = default;
+
+ android::status_t writeToParcel(android::Parcel* out) const;
+
+ android::status_t readFromParcel(const android::Parcel* in);
+
+ // These are public for ease of conversion.
+ std::vector<uint8_t> bytes;
+};
+} // Namespace os
+} // Namespace android
+
+
+#endif // STATS_LOG_EVENT_WRAPPER_H
+
diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
new file mode 100644
index 0000000..8b3aa9a
--- /dev/null
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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/os/StatsLogEventWrapper.h>
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+using android::Parcel;
+using android::Parcelable;
+using android::status_t;
+using std::vector;
+
+namespace android {
+namespace os {
+
+StatsLogEventWrapper::StatsLogEventWrapper(){};
+
+status_t StatsLogEventWrapper::writeToParcel(Parcel* out) const {
+ out->writeByteVector(bytes);
+ return ::android::NO_ERROR;
+};
+
+status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) {
+ in->readByteVector(&bytes);
+ return ::android::NO_ERROR;
+};
+
+} // Namespace os
+} // Namespace android
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 8c1010d..33c5490 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -138,9 +138,10 @@
</pre>
-<p>Note that “input” and “output” are from the standpoint of the device. So a
-synthesizer will have an “input” port that receives messages. A keyboard will
-have an “output” port that sends messages.</p>
+<p>Note that “input” and “output” directions reflect the point of view
+of the MIDI device itself, not your app.
+For example, to send MIDI notes to a synthesizer, open the synth's INPUT port.
+To receive notes from a keyboard, open the keyboard's OUTPUT port.</p>
<p>The MidiDeviceInfo has a bundle of properties.</p>
@@ -359,8 +360,10 @@
<p>MIDI devices can be connected to Android using Bluetooth LE.</p>
<p>Before using the device, the app must scan for available BTLE devices and then allow
-the user to connect. An example program
-will be provided so look for it on the Android developer website.</p>
+the user to connect.
+See the Android developer website for an
+<a href="https://source.android.com/devices/audio/midi_test#apps" target="_blank">example
+program</a>.</p>
<h2 id=btle_location_permissions>Request Location Permission for BTLE</h2>
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9f2c08e..aa0d0cc 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.media.projection.IMediaProjection;
import android.os.Handler;
import android.os.IBinder;
@@ -71,8 +73,11 @@
*/
public Intent createScreenCaptureIntent() {
Intent i = new Intent();
- i.setClassName("com.android.systemui",
- "com.android.systemui.media.MediaProjectionPermissionActivity");
+ final ComponentName mediaProjectionPermissionDialogComponent =
+ ComponentName.unflattenFromString(mContext.getResources().getString(
+ com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent));
+ i.setComponent(mediaProjectionPermissionDialogComponent);
return i;
}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 17b2326..aaf18e7 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -471,10 +471,14 @@
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
+ where = STORAGE_PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(storageID),
+ Integer.toString(parent)};
+ } else {
+ // If a parent is specified, the storage is redundant
+ where = PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(parent)};
}
- where = STORAGE_PARENT_WHERE;
- whereArgs = new String[] { Integer.toString(storageID),
- Integer.toString(parent) };
}
} else {
// query specific format
@@ -487,11 +491,16 @@
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
+ where = STORAGE_FORMAT_PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(storageID),
+ Integer.toString(format),
+ Integer.toString(parent)};
+ } else {
+ // If a parent is specified, the storage is redundant
+ where = FORMAT_PARENT_WHERE;
+ whereArgs = new String[]{Integer.toString(format),
+ Integer.toString(parent)};
}
- where = STORAGE_FORMAT_PARENT_WHERE;
- whereArgs = new String[] { Integer.toString(storageID),
- Integer.toString(format),
- Integer.toString(parent) };
}
}
}
@@ -845,7 +854,7 @@
return MtpConstants.RESPONSE_OK;
}
- private int moveObject(int handle, int newParent, String newPath) {
+ private int moveObject(int handle, int newParent, int newStorage, String newPath) {
String[] whereArgs = new String[] { Integer.toString(handle) };
// do not allow renaming any of the special subdirectories
@@ -857,6 +866,7 @@
ContentValues values = new ContentValues();
values.put(Files.FileColumns.DATA, newPath);
values.put(Files.FileColumns.PARENT, newParent);
+ values.put(Files.FileColumns.STORAGE_ID, newStorage);
int updated = 0;
try {
// note - we are relying on a special case in MediaProvider.update() to update
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 7225f10..0da6289 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -180,7 +180,7 @@
virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property);
virtual MtpResponseCode moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
- MtpString& newPath);
+ MtpStorageID newStorage, MtpString& newPath);
virtual void sessionStarted();
@@ -1000,11 +1000,11 @@
}
MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
- MtpString &newPath) {
+ MtpStorageID newStorage, MtpString &newPath) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jstring stringValue = env->NewStringUTF((const char *) newPath);
MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
- (jint)handle, (jint)newParent, stringValue);
+ (jint)handle, (jint)newParent, (jint) newStorage, stringValue);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
env->DeleteLocalRef(stringValue);
@@ -1376,7 +1376,7 @@
ALOGE("Can't find deleteFile");
return -1;
}
- method_moveObject = env->GetMethodID(clazz, "moveObject", "(IILjava/lang/String;)I");
+ method_moveObject = env->GetMethodID(clazz, "moveObject", "(IIILjava/lang/String;)I");
if (method_moveObject == NULL) {
ALOGE("Can't find moveObject");
return -1;
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index e9e9309..6ce104d 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -72,7 +72,7 @@
const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);
const char *deviceInfoSerialNumberStr = env->GetStringUTFChars(deviceInfoSerialNumber, NULL);
MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
- usePtp, AID_MEDIA_RW, 0664, 0775,
+ usePtp,
MtpString((deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : ""),
MtpString((deviceInfoModelStr != NULL) ? deviceInfoModelStr : ""),
MtpString((deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : ""),
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 3d083b1..a404759 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -525,6 +525,12 @@
<!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
<string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
+ <!-- Title of the developer option for DNS over TLS. -->
+ <string name="dns_tls">DNS over TLS</string>
+ <!-- Summary to explain the developer option for DNS over TLS. This allows the user to
+ request that the system attempt TLS with all DNS servers, or none. -->
+ <string name="dns_tls_summary">If enabled, attempt DNS over TLS on port 853.</string>
+
<!-- setting Checkbox summary whether to show options for wireless display certification -->
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
<!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
new file mode 100644
index 0000000..7162121
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -0,0 +1,8 @@
+# Default reviewers for this and subdirectories.
+asapperstein@google.com
+asargent@google.com
+eisenbach@google.com
+jackqdyulei@google.com
+siyuanh@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
new file mode 100644
index 0000000..d5d2e9e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -0,0 +1,7 @@
+# Default reviewers for this and subdirectories.
+asapperstein@google.com
+asargent@google.com
+dling@google.com
+zhfan@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index c9368f3..806a073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -115,8 +115,12 @@
boolean isStackTask = !isFreeformTask;
boolean isLaunchTarget = taskKey.id == runningTaskId;
- // Load the title, icon, and color
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
+ if (info == null) {
+ continue;
+ }
+
+ // Load the title, icon, and color
String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
String titleDescription = loader.getAndUpdateContentDescription(taskKey,
t.taskDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index b4cc4b1..8869e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -34,6 +34,7 @@
import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
/**
* Quick settings common detail view with line items.
@@ -185,7 +186,7 @@
view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
if (item.iconDrawable != null) {
- iv.setImageDrawable(item.iconDrawable);
+ iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
} else {
iv.setImageResource(item.icon);
}
@@ -258,7 +259,7 @@
public static class Item {
public int icon;
- public Drawable iconDrawable;
+ public QSTile.Icon iconDrawable;
public Drawable overlay;
public CharSequence line1;
public CharSequence line2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 81b8622..bc3ccb4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,9 @@
if (lastDevice != null) {
int batteryLevel = lastDevice.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- state.icon = new BluetoothBatteryDrawable(batteryLevel);
+ state.icon = new BluetoothBatteryDrawable(batteryLevel,
+ mContext.getResources().getFraction(
+ R.fraction.bt_battery_scale_fraction, 1, 1));
}
}
state.contentDescription = mContext.getString(
@@ -212,17 +214,21 @@
private class BluetoothBatteryDrawable extends Icon {
private int mLevel;
+ private float mIconScale;
BluetoothBatteryDrawable(int level) {
+ this(level, 1 /* iconScale */);
+ }
+
+ BluetoothBatteryDrawable(int level, float iconScale) {
mLevel = level;
+ mIconScale = iconScale;
}
@Override
public Drawable getDrawable(Context context) {
return createLayerDrawable(context,
- R.drawable.ic_qs_bluetooth_connected, mLevel,
- context.getResources().getFraction(
- R.fraction.bt_battery_scale_fraction, 1, 1));
+ R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
}
}
@@ -304,8 +310,7 @@
item.icon = R.drawable.ic_qs_bluetooth_connected;
int batteryLevel = device.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- item.iconDrawable = createLayerDrawable(mContext, item.icon,
- batteryLevel);
+ item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
item.line2 = mContext.getString(
R.string.quick_settings_connected_battery_level,
Utils.formatPercentage(batteryLevel));
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 54eba2b..ee0c043 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -336,6 +336,34 @@
// Count of connection attempts that were initiated unsuccessfully
optional int32 num_open_network_connect_message_failed_to_send = 82;
+
+ // Histogram counting instances of scans with N many HotSpot 2.0 R1 APs
+ repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_in_scan_histogram = 83;
+
+ // Histogram counting instances of scans with N many HotSpot 2.0 R2 APs
+ repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_in_scan_histogram = 84;
+
+ // Histogram counting instances of scans with N many unique HotSpot 2.0 R1 ESS.
+ // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
+ // (SSID, BSSID) tuple depending on AP configuration (in the above priority
+ // order).
+ repeated NumConnectableNetworksBucket observed_hotspot_r1_ess_in_scan_histogram = 85;
+
+ // Histogram counting instances of scans with N many unique HotSpot 2.0 R2 ESS.
+ // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
+ // (SSID, BSSID) tuple depending on AP configuration (in the above priority
+ // order).
+ repeated NumConnectableNetworksBucket observed_hotspot_r2_ess_in_scan_histogram = 86;
+
+ // Histogram counting number of HotSpot 2.0 R1 APs per observed ESS in a scan
+ // (one value added per unique ESS - potentially multiple counts per single
+ // scan!)
+ repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_per_ess_in_scan_histogram = 87;
+
+ // Histogram counting number of HotSpot 2.0 R2 APs per observed ESS in a scan
+ // (one value added per unique ESS - potentially multiple counts per single
+ // scan!)
+ repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_per_ess_in_scan_histogram = 88;
}
// Information that gets logged for every WiFi connection.
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 51afada..76e7782 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -21,9 +21,12 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
@@ -99,7 +102,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
import com.android.server.LocalServices;
import com.android.server.WidgetBackupProvider;
@@ -191,10 +193,6 @@
}
};
- // Manages active connections to RemoteViewsServices.
- private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
- mBoundRemoteViewsServices = new HashMap<>();
-
// Manages persistent references to RemoteViewsServices from different App Widgets.
private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
mRemoteViewsServicesAppWidgets = new HashMap<>();
@@ -1209,17 +1207,14 @@
}
@Override
- public void bindRemoteViewsService(String callingPackage, int appWidgetId,
- Intent intent, IBinder callbacks) {
+ public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent,
+ IApplicationThread caller, IBinder activtiyToken, IServiceConnection connection,
+ int flags) {
final int userId = UserHandle.getCallingUserId();
-
if (DEBUG) {
Slog.i(TAG, "bindRemoteViewsService() " + userId);
}
- // Make sure the package runs under the caller uid.
- mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
@@ -1254,76 +1249,35 @@
mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
componentName, widget.provider.getUserId());
- // Good to go - the service pakcage is correct, it exists for the correct
+ // Good to go - the service package is correct, it exists for the correct
// user, and requires the bind permission.
- // If there is already a connection made for this service intent, then
- // disconnect from that first. (This does not allow multiple connections
- // to the same service under the same key).
- ServiceConnectionProxy connection = null;
- FilterComparison fc = new FilterComparison(intent);
- Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ // Ask ActivityManager to bind it. Notice that we are binding the service with the
+ // caller app instead of DevicePolicyManagerService.
+ if(ActivityManager.getService().bindService(
+ caller, activtiyToken, intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ connection, flags, mContext.getOpPackageName(),
+ widget.provider.getUserId()) != 0) {
- if (mBoundRemoteViewsServices.containsKey(key)) {
- connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
- connection.disconnect();
- unbindService(connection);
- mBoundRemoteViewsServices.remove(key);
- }
-
- // Bind to the RemoteViewsService (which will trigger a callback to the
- // RemoteViewsAdapter.onServiceConnected())
- connection = new ServiceConnectionProxy(callbacks);
- bindService(intent, connection, widget.provider.info.getProfile());
- mBoundRemoteViewsServices.put(key, connection);
-
- // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
- // can determine when we can call back to the RemoteViewsService later to
- // destroy associated factories.
- Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
- incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
- }
- }
-
- @Override
- public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
- final int userId = UserHandle.getCallingUserId();
-
- if (DEBUG) {
- Slog.i(TAG, "unbindRemoteViewsService() " + userId);
- }
-
- // Make sure the package runs under the caller uid.
- mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
- synchronized (mLock) {
- ensureGroupStateLoadedLocked(userId);
-
- // Unbind from the RemoteViewsService (which will trigger a callback to the bound
- // RemoteViewsAdapter)
- Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
- new FilterComparison(intent));
- if (mBoundRemoteViewsServices.containsKey(key)) {
- // We don't need to use the appWidgetId until after we are sure there is something
- // to unbind. Note that this may mask certain issues with apps calling unbind()
- // more than necessary.
-
- // NOTE: The lookup is enforcing security across users by making
- // sure the caller can only access widgets it hosts or provides.
- Widget widget = lookupWidgetLocked(appWidgetId,
- Binder.getCallingUid(), callingPackage);
-
- if (widget == null) {
- throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+ // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+ // can determine when we can call back to the RemoteViewsService later to
+ // destroy associated factories.
+ incrementAppWidgetServiceRefCount(appWidgetId,
+ Pair.create(widget.provider.id.uid, new FilterComparison(intent)));
+ return true;
}
-
- ServiceConnectionProxy connection = (ServiceConnectionProxy)
- mBoundRemoteViewsServices.get(key);
- connection.disconnect();
- mContext.unbindService(connection);
- mBoundRemoteViewsServices.remove(key);
+ } catch (RemoteException ex) {
+ // Same process, should not happen.
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
}
}
+
+ // Failed to bind.
+ return false;
}
@Override
@@ -1754,7 +1708,9 @@
private void deleteAppWidgetLocked(Widget widget) {
// We first unbind all services that are bound to this id
- unbindAppWidgetRemoteViewsServicesLocked(widget);
+ // Check if we need to destroy any services (if no other app widgets are
+ // referencing the same service)
+ decrementAppWidgetServiceRefCount(widget);
Host host = widget.host;
host.widgets.remove(widget);
@@ -1796,28 +1752,6 @@
}
}
- // Unbinds from a RemoteViewsService when we delete an app widget
- private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
- int appWidgetId = widget.appWidgetId;
- // Unbind all connections to Services bound to this AppWidgetId
- Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
- .iterator();
- while (it.hasNext()) {
- final Pair<Integer, Intent.FilterComparison> key = it.next();
- if (key.first == appWidgetId) {
- final ServiceConnectionProxy conn = (ServiceConnectionProxy)
- mBoundRemoteViewsServices.get(key);
- conn.disconnect();
- mContext.unbindService(conn);
- it.remove();
- }
- }
-
- // Check if we need to destroy any services (if no other app widgets are
- // referencing the same service)
- decrementAppWidgetServiceRefCount(widget);
- }
-
// Destroys the cached factory on the RemoteViewsService's side related to the specified intent
private void destroyRemoteViewsService(final Intent intent, Widget widget) {
final ServiceConnection conn = new ServiceConnection() {
@@ -1853,7 +1787,7 @@
// Adds to the ref-count for a given RemoteViewsService intent
private void incrementAppWidgetServiceRefCount(int appWidgetId,
Pair<Integer, FilterComparison> serviceId) {
- HashSet<Integer> appWidgetIds = null;
+ final HashSet<Integer> appWidgetIds;
if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
} else {
@@ -4055,40 +3989,6 @@
}
}
- /**
- * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
- * needs to be a static inner class since a reference to the ServiceConnection is held globally
- * and may lead us to leak AppWidgetService instances (if there were more than one).
- */
- private static final class ServiceConnectionProxy implements ServiceConnection {
- private final IRemoteViewsAdapterConnection mConnectionCb;
-
- ServiceConnectionProxy(IBinder connectionCb) {
- mConnectionCb = IRemoteViewsAdapterConnection.Stub
- .asInterface(connectionCb);
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- try {
- mConnectionCb.onServiceConnected(service);
- } catch (RemoteException re) {
- Slog.e(TAG, "Error passing service interface", re);
- }
- }
-
- public void onServiceDisconnected(ComponentName name) {
- disconnect();
- }
-
- public void disconnect() {
- try {
- mConnectionCb.onServiceDisconnected();
- } catch (RemoteException re) {
- Slog.e(TAG, "Error clearing service interface", re);
- }
- }
- }
-
private class LoadedWidgetState {
final Widget widget;
final int hostTag;
@@ -4642,7 +4542,9 @@
// reconstructed due to the restore
host.widgets.remove(widget);
provider.widgets.remove(widget);
- unbindAppWidgetRemoteViewsServicesLocked(widget);
+ // Check if we need to destroy any services (if no other app widgets are
+ // referencing the same service)
+ decrementAppWidgetServiceRefCount(widget);
removeWidgetLocked(widget);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 880f236..075c741 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -52,6 +52,7 @@
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.LocalLog;
@@ -338,6 +339,8 @@
return;
}
+ session.logContextCommittedLocked();
+
final boolean finished = session.showSaveLocked();
if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
@@ -563,8 +566,9 @@
void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
synchronized (mLock) {
if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
- mEventHistory
- .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState));
+ mEventHistory.addEvent(
+ new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
+ null, null, null, null));
}
}
}
@@ -578,7 +582,7 @@
if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
- clientState));
+ clientState, null, null, null, null, null, null));
}
}
}
@@ -589,7 +593,8 @@
void logSaveShown(int sessionId, @Nullable Bundle clientState) {
synchronized (mLock) {
if (isValidEventLocked("logSaveShown()", sessionId)) {
- mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState));
+ mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
+ null, null, null, null, null));
}
}
}
@@ -602,7 +607,28 @@
synchronized (mLock) {
if (isValidEventLocked("logDatasetSelected()", sessionId)) {
mEventHistory.addEvent(
- new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState));
+ new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
+ null, null, null, null, null));
+ }
+ }
+ }
+
+ /**
+ * Updates the last fill response when an autofill context is committed.
+ */
+ void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+ @Nullable ArrayList<String> selectedDatasets,
+ @Nullable ArraySet<String> ignoredDatasets,
+ @Nullable ArrayList<AutofillId> changedFieldIds,
+ @Nullable ArrayList<String> changedDatasetIds,
+ @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+ @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+ synchronized (mLock) {
+ if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+ mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+ clientState, selectedDatasets, ignoredDatasets,
+ changedFieldIds, changedDatasetIds,
+ manuallyFilledFieldIds, manuallyFilledDatasetIds));
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3c12d67..b720f74 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -54,6 +54,7 @@
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.InternalSanitizer;
@@ -90,6 +91,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -840,6 +842,191 @@
}
/**
+ * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
+ * when necessary.
+ */
+ public void logContextCommittedLocked() {
+ final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
+ if (lastResponse == null) return;
+
+ final int flags = lastResponse.getFlags();
+ if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
+ if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
+ return;
+ }
+
+ ArraySet<String> ignoredDatasets = null;
+ ArrayList<AutofillId> changedFieldIds = null;
+ ArrayList<String> changedDatasetIds = null;
+ ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
+
+ boolean hasAtLeastOneDataset = false;
+ final int responseCount = mResponses.size();
+ for (int i = 0; i < responseCount; i++) {
+ final FillResponse response = mResponses.valueAt(i);
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets == null || datasets.isEmpty()) {
+ if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
+ } else {
+ for (int j = 0; j < datasets.size(); j++) {
+ final Dataset dataset = datasets.get(j);
+ final String datasetId = dataset.getId();
+ if (datasetId == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
+ }
+ } else {
+ hasAtLeastOneDataset = true;
+ if (mSelectedDatasetIds == null
+ || !mSelectedDatasetIds.contains(datasetId)) {
+ if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
+ if (ignoredDatasets == null) {
+ ignoredDatasets = new ArraySet<>();
+ }
+ ignoredDatasets.add(datasetId);
+ }
+ }
+ }
+ }
+ }
+ if (!hasAtLeastOneDataset) {
+ if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets)");
+ return;
+ }
+
+ for (int i = 0; i < mViewStates.size(); i++) {
+ final ViewState viewState = mViewStates.valueAt(i);
+ final int state = viewState.getState();
+
+ // When value changed, we need to log if it was:
+ // - autofilled -> changedDatasetIds
+ // - not autofilled but matches a dataset value -> manuallyFilledIds
+ if ((state & ViewState.STATE_CHANGED) != 0) {
+
+ // Check if autofilled value was changed
+ if ((state & ViewState.STATE_AUTOFILLED) != 0) {
+ final String datasetId = viewState.getDatasetId();
+ if (datasetId == null) {
+ // Sanity check - should never happen.
+ Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
+ continue;
+ }
+
+ // Must first check if final changed value is not the same as value sent by
+ // service.
+ final AutofillValue autofilledValue = viewState.getAutofilledValue();
+ final AutofillValue currentValue = viewState.getCurrentValue();
+ if (autofilledValue != null && autofilledValue.equals(currentValue)) {
+ if (sDebug) {
+ Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
+ + " because it has same value that was autofilled");
+ }
+ continue;
+ }
+
+ if (sDebug) {
+ Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
+ }
+ if (changedFieldIds == null) {
+ changedFieldIds = new ArrayList<>();
+ changedDatasetIds = new ArrayList<>();
+ }
+ changedFieldIds.add(viewState.id);
+ changedDatasetIds.add(datasetId);
+ } else {
+ // Check if value match a dataset.
+ final AutofillValue currentValue = viewState.getCurrentValue();
+ if (currentValue == null) {
+ if (sDebug) {
+ Slog.d(TAG, "logContextCommitted(): skipping view witout current value "
+ + "( " + viewState + ")");
+ }
+ continue;
+ }
+ for (int j = 0; j < responseCount; j++) {
+ final FillResponse response = mResponses.valueAt(j);
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets == null || datasets.isEmpty()) {
+ if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + j);
+ } else {
+ for (int k = 0; k < datasets.size(); k++) {
+ final Dataset dataset = datasets.get(k);
+ final String datasetId = dataset.getId();
+ if (datasetId == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommitted() skipping idless dataset "
+ + dataset);
+ }
+ } else {
+ final ArrayList<AutofillValue> values = dataset.getFieldValues();
+ for (int l = 0; l < values.size(); l++) {
+ final AutofillValue candidate = values.get(l);
+ if (currentValue.equals(candidate)) {
+ if (sDebug) {
+ Slog.d(TAG, "field " + viewState.id
+ + " was manually filled with value set by "
+ + "dataset " + datasetId);
+ }
+ if (manuallyFilledIds == null) {
+ manuallyFilledIds = new ArrayMap<>();
+ }
+ ArraySet<String> datasetIds =
+ manuallyFilledIds.get(viewState.id);
+ if (datasetIds == null) {
+ datasetIds = new ArraySet<>(1);
+ manuallyFilledIds.put(viewState.id, datasetIds);
+ }
+ datasetIds.add(datasetId);
+ }
+ }
+ if (mSelectedDatasetIds == null
+ || !mSelectedDatasetIds.contains(datasetId)) {
+ if (sVerbose) {
+ Slog.v(TAG, "adding ignored dataset " + datasetId);
+ }
+ if (ignoredDatasets == null) {
+ ignoredDatasets = new ArraySet<>();
+ }
+ ignoredDatasets.add(datasetId);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (sVerbose) {
+ Slog.v(TAG, "logContextCommitted(): id=" + id
+ + ", selectedDatasetids=" + mSelectedDatasetIds
+ + ", ignoredDatasetIds=" + ignoredDatasets
+ + ", changedAutofillIds=" + changedFieldIds
+ + ", changedDatasetIds=" + changedDatasetIds
+ + ", manuallyFilledIds=" + manuallyFilledIds);
+ }
+
+ ArrayList<AutofillId> manuallyFilledFieldIds = null;
+ ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
+
+ // Must "flatten" the map to the parcellable collection primitives
+ if (manuallyFilledIds != null) {
+ final int size = manuallyFilledIds.size();
+ manuallyFilledFieldIds = new ArrayList<>(size);
+ manuallyFilledDatasetIds = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ final AutofillId fieldId = manuallyFilledIds.keyAt(i);
+ final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
+ manuallyFilledFieldIds.add(fieldId);
+ manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
+ }
+ }
+ mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
+ changedFieldIds, changedDatasetIds,
+ manuallyFilledFieldIds, manuallyFilledDatasetIds);
+ }
+
+ /**
* Shows the save UI, when session can be saved.
*
* @return {@code true} if session is done, or {@code false} if it's pending user action.
@@ -970,6 +1157,7 @@
boolean isValid;
try {
isValid = validator.isValid(valueFinder);
+ if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
log.setType(isValid
? MetricsEvent.TYPE_SUCCESS
: MetricsEvent.TYPE_DISMISS);
@@ -1408,11 +1596,10 @@
* Checks whether a view should be ignored.
*/
private boolean isIgnoredLocked(AutofillId id) {
- if (mResponses == null || mResponses.size() == 0) {
- return false;
- }
// Always check the latest response only
- final FillResponse response = mResponses.valueAt(mResponses.size() - 1);
+ final FillResponse response = getLastResponseLocked(null);
+ if (response == null) return false;
+
return ArrayUtils.contains(response.getIgnoredIds(), id);
}
@@ -1489,13 +1676,10 @@
}
private void updateTrackedIdsLocked() {
- if (mResponses == null || mResponses.size() == 0) {
- return;
- }
-
// Only track the views of the last response as only those are reported back to the
// service, see #showSaveLocked
- final FillResponse response = mResponses.valueAt(getLastResponseIndexLocked());
+ final FillResponse response = getLastResponseLocked(null);
+ if (response == null) return;
ArraySet<AutofillId> trackedViews = null;
boolean saveOnAllViewsInvisible = false;
@@ -1600,7 +1784,10 @@
}
if (mResponses == null) {
- mResponses = new SparseArray<>(4);
+ // Set initial capacity as 2 to handle cases where service always requires auth.
+ // TODO: add a metric for number of responses set by server, so we can use its average
+ // as the initial array capacitiy.
+ mResponses = new SparseArray<>(2);
}
mResponses.put(requestId, newResponse);
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
@@ -1676,6 +1863,10 @@
final AutofillId id = ids.get(j);
final AutofillValue value = values.get(j);
final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
+ final String datasetId = dataset.getId();
+ if (datasetId != null) {
+ viewState.setDatasetId(datasetId);
+ }
if (response != null) {
viewState.setResponse(response);
} else if (clearResponse) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 51659bb..1d8110f 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -78,6 +78,7 @@
private AutofillValue mAutofilledValue;
private Rect mVirtualBounds;
private int mState;
+ private String mDatasetId;
ViewState(Session session, AutofillId id, Listener listener, int state) {
mSession = session;
@@ -148,6 +149,15 @@
mState &= ~state;
}
+ @Nullable
+ String getDatasetId() {
+ return mDatasetId;
+ }
+
+ void setDatasetId(String datasetId) {
+ mDatasetId = datasetId;
+ }
+
// TODO: refactor / rename / document this method (and maybeCallOnFillReady) to make it clear
// that it can change the value and update the UI; similarly, should replace code that
// directly sets mAutofillValue to use encapsulation.
@@ -182,13 +192,15 @@
@Override
public String toString() {
- return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
+ return "ViewState: [id=" + id + ", datasetId=" + mDatasetId
+ + ", currentValue=" + mCurrentValue
+ ", autofilledValue=" + mAutofilledValue
+ ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
}
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("id:" ); pw.println(this.id);
+ pw.print(prefix); pw.print("datasetId:" ); pw.println(this.mDatasetId);
pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
pw.print(prefix); pw.print("response:");
if (mResponse == null) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index bf442dc..6d3d792 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -157,6 +157,11 @@
final int index = dataset.getFieldIds().indexOf(focusedViewId);
if (index >= 0) {
final RemoteViews presentation = dataset.getFieldPresentation(index);
+ if (presentation == null) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ + "service didn't provide a presentation for it on " + dataset);
+ continue;
+ }
final View view;
try {
if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 1659133..633bb3e 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -6,6 +6,7 @@
LOCAL_AIDL_INCLUDES := \
frameworks/native/aidl/binder \
+ system/core/storaged/binder \
system/netd/server/binder \
system/vold/binder
@@ -13,6 +14,7 @@
$(call all-java-files-under,java) \
java/com/android/server/EventLogTags.logtags \
java/com/android/server/am/EventLogTags.logtags \
+ ../../../../system/core/storaged/binder/android/os/IStoraged.aidl \
../../../../system/netd/server/binder/android/net/INetd.aidl \
../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \
../../../../system/vold/binder/android/os/IVold.aidl \
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 47be0a7..46b671b 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -39,10 +39,12 @@
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V2_0.IHealthInfoCallback;
import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
-import android.os.BatteryProperties;
+import android.os.BatteryProperty;
import android.os.Binder;
import android.os.FileUtils;
import android.os.Handler;
@@ -58,6 +60,7 @@
import android.provider.Settings;
import android.service.battery.BatteryServiceDumpProto;
import android.util.EventLog;
+import android.util.MutableInt;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -70,6 +73,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicReference;
/**
* <p>BatteryService monitors the charging status, and charge level of the device
@@ -108,6 +112,8 @@
private static final int BATTERY_SCALE = 100; // battery capacity is a percentage
+ private static final long HEALTH_HAL_WAIT_MS = 1000;
+
// Used locally for determining when to make a last ditch effort to log
// discharge stats before the device dies.
private int mCriticalBatteryLevel;
@@ -165,6 +171,10 @@
private ActivityManagerInternal mActivityManagerInternal;
+ private HealthServiceWrapper mHealthServiceWrapper;
+ private HealthHalCallback mHealthHalCallback;
+ private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
public BatteryService(Context context) {
super(context);
@@ -203,17 +213,12 @@
@Override
public void onStart() {
- IBinder b = ServiceManager.getService("batteryproperties");
- final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
- IBatteryPropertiesRegistrar.Stub.asInterface(b);
- try {
- batteryPropertiesRegistrar.registerListener(new BatteryListener());
- } catch (RemoteException e) {
- // Should never happen.
- }
+ registerHealthCallback();
mBinderService = new BinderService();
publishBinderService("battery", mBinderService);
+ mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
+ publishBinderService("batteryproperties", mBatteryPropertiesRegistrar);
publishLocalService(BatteryManagerInternal.class, new LocalService());
}
@@ -239,6 +244,49 @@
}
}
+ private void registerHealthCallback() {
+ mHealthServiceWrapper = new HealthServiceWrapper();
+ mHealthHalCallback = new HealthHalCallback();
+ // IHealth is lazily retrieved.
+ try {
+ mHealthServiceWrapper.init(mHealthHalCallback,
+ new HealthServiceWrapper.IServiceManagerSupplier() {},
+ new HealthServiceWrapper.IHealthSupplier() {});
+ } catch (RemoteException | NoSuchElementException ex) {
+ Slog.w(TAG, "health: cannot register callback. "
+ + "BatteryService will be started with dummy values. Reason: "
+ + ex.getClass().getSimpleName() + ": " + ex.getMessage());
+ update(new HealthInfo());
+ return;
+ }
+
+ // init register for new service notifications, and IServiceManager should return the
+ // existing service in a near future. Wait for this.update() to instantiate
+ // the initial mHealthInfo.
+ long timeWaited = 0;
+ synchronized (mLock) {
+ long beforeWait = SystemClock.uptimeMillis();
+ while (mHealthInfo == null &&
+ (timeWaited = SystemClock.uptimeMillis() - beforeWait) < HEALTH_HAL_WAIT_MS) {
+ try {
+ mLock.wait(HEALTH_HAL_WAIT_MS - timeWaited);
+ } catch (InterruptedException ex) {
+ break;
+ }
+ }
+ if (mHealthInfo == null) {
+ Slog.w(TAG, "health: Waited " + timeWaited + "ms for callbacks but received "
+ + "nothing. BatteryService will be started with dummy values.");
+ update(new HealthInfo());
+ return;
+ }
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "health: Waited " + timeWaited + "ms and received the update.");
+ }
+ }
+
private void updateBatteryWarningLevelLocked() {
final ContentResolver resolver = mContext.getContentResolver();
int defWarnLevel = mContext.getResources().getInteger(
@@ -331,15 +379,15 @@
}
}
- private void update(BatteryProperties props) {
+ private void update(HealthInfo info) {
synchronized (mLock) {
if (!mUpdatesStopped) {
- mHealthInfo = new HealthInfo();
- copy(mHealthInfo, props);
+ mHealthInfo = info;
// Process the new values.
processValuesLocked(false);
+ mLock.notifyAll(); // for any waiters on new info
} else {
- copy(mLastHealthInfo, props);
+ copy(mLastHealthInfo, info);
}
}
}
@@ -366,24 +414,6 @@
dst.energyCounter = src.energyCounter;
}
- // TODO(b/62229583): remove this function when BatteryProperties are completely replaced.
- private static void copy(HealthInfo dst, BatteryProperties src) {
- dst.legacy.chargerAcOnline = src.chargerAcOnline;
- dst.legacy.chargerUsbOnline = src.chargerUsbOnline;
- dst.legacy.chargerWirelessOnline = src.chargerWirelessOnline;
- dst.legacy.maxChargingCurrent = src.maxChargingCurrent;
- dst.legacy.maxChargingVoltage = src.maxChargingVoltage;
- dst.legacy.batteryStatus = src.batteryStatus;
- dst.legacy.batteryHealth = src.batteryHealth;
- dst.legacy.batteryPresent = src.batteryPresent;
- dst.legacy.batteryLevel = src.batteryLevel;
- dst.legacy.batteryVoltage = src.batteryVoltage;
- dst.legacy.batteryTemperature = src.batteryTemperature;
- dst.legacy.batteryFullCharge = src.batteryFullCharge;
- dst.legacy.batteryChargeCounter = src.batteryChargeCounter;
- dst.legacy.batteryTechnology = src.batteryTechnology;
- }
-
private void processValuesLocked(boolean force) {
boolean logOutlier = false;
long dischargeDuration = 0;
@@ -962,15 +992,43 @@
}
}
- private final class BatteryListener extends IBatteryPropertiesListener.Stub {
- @Override public void batteryPropertiesChanged(BatteryProperties props) {
- final long identity = Binder.clearCallingIdentity();
+ private final class HealthHalCallback extends IHealthInfoCallback.Stub
+ implements HealthServiceWrapper.Callback {
+ @Override public void healthInfoChanged(HealthInfo props) {
+ BatteryService.this.update(props);
+ }
+ // on new service registered
+ @Override public void onRegistration(IHealth oldService, IHealth newService,
+ String instance) {
+ if (newService == null) return;
+
try {
- BatteryService.this.update(props);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ if (oldService != null) {
+ int r = oldService.unregisterCallback(this);
+ if (r != Result.SUCCESS) {
+ Slog.w(TAG, "health: cannot unregister previous callback: " +
+ Result.toString(r));
+ }
+ }
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
+ + ex.getMessage());
}
- }
+
+ try {
+ int r = newService.registerCallback(this);
+ if (r != Result.SUCCESS) {
+ Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
+ return;
+ }
+ // registerCallback does NOT guarantee that update is called
+ // immediately, so request a manual update here.
+ newService.update();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "health: cannot register callback (transaction error): "
+ + ex.getMessage());
+ }
+ }
}
private final class BinderService extends Binder {
@@ -991,6 +1049,63 @@
}
}
+ // Reduced IBatteryPropertiesRegistrar that only implements getProperty for usage
+ // in BatteryManager.
+ private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
+ public void registerListener(IBatteryPropertiesListener listener) {
+ Slog.e(TAG, "health: must not call registerListener on battery properties");
+ }
+ public void unregisterListener(IBatteryPropertiesListener listener) {
+ Slog.e(TAG, "health: must not call unregisterListener on battery properties");
+ }
+ public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+ IHealth service = mHealthServiceWrapper.getLastService();
+ final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+ switch(id) {
+ case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+ service.getChargeCounter((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+ service.getCurrentNow((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+ service.getCurrentAverage((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+ service.getCapacity((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_STATUS:
+ service.getChargeStatus((int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+ service.getEnergyCounter((int result, long value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ }
+ return outResult.value;
+ }
+ public void scheduleUpdate() {
+ Slog.e(TAG, "health: must not call scheduleUpdate on battery properties");
+ }
+ }
+
private final class LocalService extends BatteryManagerInternal {
@Override
public boolean isPowered(int plugTypeSet) {
@@ -1053,6 +1168,11 @@
private Callback mCallback;
private IHealthSupplier mHealthSupplier;
+ private final Object mLastServiceSetLock = new Object();
+ // Last IHealth service received.
+ // set must be also be guarded with mLastServiceSetLock to ensure ordering.
+ private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
+
/**
* init should be called after constructor. For testing purposes, init is not called by
* constructor.
@@ -1060,6 +1180,10 @@
HealthServiceWrapper() {
}
+ IHealth getLastService() {
+ return mLastService.get();
+ }
+
/**
* Start monitoring registration of new IHealth services. Only instances that are in
* {@code sAllInstances} and in device / framework manifest are used. This function should
@@ -1109,7 +1233,7 @@
* into service.
* @param instance instance name.
*/
- void onRegistration(IHealth service, String instance);
+ void onRegistration(IHealth oldService, IHealth newService, String instance);
}
/**
@@ -1117,14 +1241,18 @@
* Must not return null; throw {@link NoSuchElementException} if a service is not available.
*/
interface IServiceManagerSupplier {
- IServiceManager get() throws NoSuchElementException, RemoteException;
+ default IServiceManager get() throws NoSuchElementException, RemoteException {
+ return IServiceManager.getService();
+ }
}
/**
* Supplier of services.
* Must not return null; throw {@link NoSuchElementException} if a service is not available.
*/
interface IHealthSupplier {
- IHealth get(String instanceName) throws NoSuchElementException, RemoteException;
+ default IHealth get(String name) throws NoSuchElementException, RemoteException {
+ return IHealth.getService(name);
+ }
}
private class Notification extends IServiceNotification.Stub {
@@ -1134,9 +1262,13 @@
if (!IHealth.kInterfaceName.equals(interfaceName)) return;
if (!sAllInstances.contains(instanceName)) return;
try {
- IHealth service = mHealthSupplier.get(instanceName);
- Slog.i(TAG, "health: new instance registered " + instanceName);
- mCallback.onRegistration(service, instanceName);
+ // ensures the order of multiple onRegistration on different threads.
+ synchronized (mLastServiceSetLock) {
+ IHealth newService = mHealthSupplier.get(instanceName);
+ IHealth oldService = mLastService.getAndSet(newService);
+ Slog.i(TAG, "health: new instance registered " + instanceName);
+ mCallback.onRegistration(oldService, newService, instanceName);
+ }
} catch (NoSuchElementException | RemoteException ex) {
Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
ex.getMessage() + ". Perhaps no permission?");
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 2e1f142..cf1d33c 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -882,8 +882,14 @@
for (int direction : DIRECTIONS) {
IpSecAlgorithm crypt = config.getEncryption(direction);
IpSecAlgorithm auth = config.getAuthentication(direction);
- if (crypt == null && auth == null) {
- throw new IllegalArgumentException("Encryption and Authentication are both null");
+ IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
+ if (authenticatedEncryption == null && crypt == null && auth == null) {
+ throw new IllegalArgumentException(
+ "No Encryption or Authentication algorithms specified");
+ } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
+ throw new IllegalArgumentException(
+ "Authenticated Encryption is mutually"
+ + " exclusive with other Authentication or Encryption algorithms");
}
if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
@@ -922,6 +928,7 @@
for (int direction : DIRECTIONS) {
IpSecAlgorithm auth = c.getAuthentication(direction);
IpSecAlgorithm crypt = c.getEncryption(direction);
+ IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
int spi = spis[direction].getSpi();
@@ -942,6 +949,9 @@
(crypt != null) ? crypt.getName() : "",
(crypt != null) ? crypt.getKey() : null,
(crypt != null) ? crypt.getTruncationLengthBits() : 0,
+ (authCrypt != null) ? authCrypt.getName() : "",
+ (authCrypt != null) ? authCrypt.getKey() : null,
+ (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
encapType,
encapLocalPort,
encapRemotePort);
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index ba3afc3..c60d7b0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1980,7 +1980,8 @@
final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
- final boolean useTls = false;
+ final boolean useTls = Settings.Global.getInt(resolver,
+ Settings.Global.DNS_TLS_DISABLED, 0) == 0;
final String tlsHostname = "";
final String[] tlsFingerprints = new String[0];
try {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 55391b3..cd25610 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IStoraged;
import android.os.IVold;
import android.os.IVoldListener;
import android.os.IVoldTaskListener;
@@ -395,6 +396,7 @@
private final Context mContext;
private volatile IVold mVold;
+ private volatile IStoraged mStoraged;
private volatile boolean mSystemReady = false;
private volatile boolean mBootCompleted = false;
@@ -809,6 +811,7 @@
}
for (int userId : systemUnlockedUsers) {
mVold.onUserStarted(userId);
+ mStoraged.onUserStarted(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -824,6 +827,7 @@
// bind mount against.
try {
mVold.onUserStarted(userId);
+ mStoraged.onUserStarted(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -850,6 +854,7 @@
try {
mVold.onUserStopped(userId);
+ mStoraged.onUserStopped(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -1335,13 +1340,36 @@
}
private void connect() {
- IBinder binder = ServiceManager.getService("vold");
+ IBinder binder = ServiceManager.getService("storaged");
+ if (binder != null) {
+ try {
+ binder.linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Slog.w(TAG, "storaged died; reconnecting");
+ mStoraged = null;
+ connect();
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ binder = null;
+ }
+ }
+
+ if (binder != null) {
+ mStoraged = IStoraged.Stub.asInterface(binder);
+ } else {
+ Slog.w(TAG, "storaged not found; trying again");
+ }
+
+ binder = ServiceManager.getService("vold");
if (binder != null) {
try {
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Slog.w(TAG, "vold died; reconnecting");
+ mVold = null;
connect();
}
}, 0);
@@ -1354,18 +1382,21 @@
mVold = IVold.Stub.asInterface(binder);
try {
mVold.setListener(mListener);
- onDaemonConnected();
- return;
} catch (RemoteException e) {
+ mVold = null;
Slog.w(TAG, "vold listener rejected; trying again", e);
}
} else {
Slog.w(TAG, "vold not found; trying again");
}
- BackgroundThread.getHandler().postDelayed(() -> {
- connect();
- }, DateUtils.SECOND_IN_MILLIS);
+ if (mStoraged == null || mVold == null) {
+ BackgroundThread.getHandler().postDelayed(() -> {
+ connect();
+ }, DateUtils.SECOND_IN_MILLIS);
+ } else {
+ onDaemonConnected();
+ }
}
private void systemReady() {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 4e15e5d..c684032 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2248,12 +2248,10 @@
Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+ response);
}
- Bundle result2 = new Bundle();
- result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
try {
- response.onResult(result2);
+ response.onResult(result);
} catch (RemoteException e) {
- // ignore
+ Slog.e(TAG, "Error calling onResult()", e);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 6ed0555..089db87 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -20,6 +20,7 @@
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 static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -168,9 +169,9 @@
} else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
return (T) mSplitScreenPrimaryStack;
}
+
for (int i = mStacks.size() - 1; i >= 0; --i) {
final ActivityStack stack = mStacks.get(i);
- // TODO: Should undefined windowing and activity type be compatible with standard type?
if (stack.isCompatible(windowingMode, activityType)) {
return (T) stack;
}
@@ -178,15 +179,28 @@
return null;
}
+ private boolean alwaysCreateStack(int windowingMode, int activityType) {
+ // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
+ // modes so that we can manage visual ordering and return types correctly.
+ return activityType == ACTIVITY_TYPE_STANDARD
+ && (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_FREEFORM
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ }
+
/**
+ * Returns an existing stack compatible with the windowing mode and activity type or creates one
+ * if a compatible stack doesn't exist.
* @see #getStack(int, int)
* @see #createStack(int, int, boolean)
*/
<T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
boolean onTop) {
- T stack = getStack(windowingMode, activityType);
- if (stack != null) {
- return stack;
+ if (!alwaysCreateStack(windowingMode, activityType)) {
+ T stack = getStack(windowingMode, activityType);
+ if (stack != null) {
+ return stack;
+ }
}
return createStack(windowingMode, activityType, onTop);
}
@@ -238,17 +252,7 @@
}
}
- final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
- if (!inSplitScreenMode
- && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
- // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
- // trying to launch in split-screen secondary.
- windowingMode = WINDOWING_MODE_FULLSCREEN;
- } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
- && WindowConfiguration.supportSplitScreenWindowingMode(
- windowingMode, activityType)) {
- windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
+ windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
final int stackId = mSupervisor.getNextStackId();
@@ -420,6 +424,21 @@
return mPinnedStack != null;
}
+ int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
+ final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+ if (!inSplitScreenMode
+ && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+ // trying to launch in split-screen secondary.
+ return WINDOWING_MODE_FULLSCREEN;
+ } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+ && WindowConfiguration.supportSplitScreenWindowingMode(
+ windowingMode, activityType)) {
+ return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+ return windowingMode;
+ }
+
@Override
public String toString() {
return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f17c9ac..e1e53b3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4877,7 +4877,7 @@
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
- return mStackSupervisor.startActivityFromRecentsInner(taskId, bOptions);
+ return mStackSupervisor.startActivityFromRecents(taskId, bOptions);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -14890,6 +14890,10 @@
synchronized (this) {
dumpActivityStarterLocked(pw, dumpPackage);
}
+ } else if ("containers".equals(cmd)) {
+ synchronized (this) {
+ dumpActivityContainersLocked(pw);
+ }
} else if ("recents".equals(cmd) || "r".equals(cmd)) {
synchronized (this) {
if (mRecentTasks != null) {
@@ -15132,6 +15136,11 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
+ dumpActivityContainersLocked(pw);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
if (mAssociations.size() > 0) {
pw.println();
@@ -15204,6 +15213,11 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
+ dumpActivityContainersLocked(pw);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
if (mAssociations.size() > 0) {
pw.println();
@@ -15236,6 +15250,12 @@
}
}
+ private void dumpActivityContainersLocked(PrintWriter pw) {
+ pw.println("ACTIVITY MANAGER STARTER (dumpsys activity containers)");
+ mStackSupervisor.dumpChildrenNames(pw, " ");
+ pw.println(" ");
+ }
+
private void dumpActivityStarterLocked(PrintWriter pw, String dumpPackage) {
pw.println("ACTIVITY MANAGER STARTER (dumpsys activity starter)");
mActivityStarter.dump(pw, "", dumpPackage);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f0811dd..941c371 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -480,6 +480,29 @@
}
}
+ @Override
+ public boolean isCompatible(int windowingMode, int activityType) {
+ // TODO: Should we just move this to ConfigurationContainer?
+ if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+ // Undefined activity types end up in a standard stack once the stack is created on a
+ // display, so they should be considered compatible.
+ activityType = ACTIVITY_TYPE_STANDARD;
+ }
+ final ActivityDisplay display = getDisplay();
+ if (display != null) {
+ if (activityType == ACTIVITY_TYPE_STANDARD
+ && windowingMode == WINDOWING_MODE_UNDEFINED) {
+ // Standard activity types will mostly take on the windowing mode of the display if
+ // one isn't specified, so look-up a compatible stack based on the display's
+ // windowing mode.
+ windowingMode = display.getWindowingMode();
+ }
+ windowingMode =
+ display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+ }
+ return super.isCompatible(windowingMode, activityType);
+ }
+
/** Adds the stack to specified display and calls WindowManager to do the same. */
void reparent(ActivityDisplay activityDisplay, boolean onTop) {
removeFromDisplay();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5c91e3c..c15b5e2 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -706,7 +706,7 @@
}
TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
- return anyTaskForIdLocked(id, matchMode, null);
+ return anyTaskForIdLocked(id, matchMode, null, !ON_TOP);
}
/**
@@ -714,11 +714,11 @@
* @param id Id of the task we would like returned.
* @param matchMode The mode to match the given task id in.
* @param aOptions The activity options to use for restoration. Can be null.
+ * @param onTop If the stack for the task should be the topmost on the display.
*/
TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode,
- @Nullable ActivityOptions aOptions) {
- // If there is a stack id set, ensure that we are attempting to actually restore a task
- // TODO: Don't really know if this is needed...
+ @Nullable ActivityOptions aOptions, boolean onTop) {
+ // If options are set, ensure that we are attempting to actually restore a task
if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
throw new IllegalArgumentException("Should not specify activity options for non-restore"
+ " lookup");
@@ -730,9 +730,21 @@
for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getChildAt(stackNdx);
final TaskRecord task = stack.taskForIdLocked(id);
- if (task != null) {
- return task;
+ if (task == null) {
+ continue;
}
+ if (aOptions != null) {
+ // Resolve the stack the task should be placed in now based on options
+ // and reparent if needed.
+ final ActivityStack launchStack = getLaunchStack(null, aOptions, task, onTop);
+ if (launchStack != null && stack != launchStack) {
+ final int reparentMode = onTop
+ ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
+ task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
+ "anyTaskForIdLocked");
+ }
+ }
+ return task;
}
}
@@ -759,7 +771,7 @@
}
// Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
- if (!restoreRecentTaskLocked(task, aOptions)) {
+ if (!restoreRecentTaskLocked(task, aOptions, onTop)) {
if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
"Couldn't restore task id=" + id + " found in recents");
return null;
@@ -2248,11 +2260,13 @@
}
}
- if (isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
supportsFreeform, supportsPip, activityType)) {
return windowingMode;
}
- return WINDOWING_MODE_FULLSCREEN;
+ // Return root/systems windowing mode
+ return getWindowingMode();
}
int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
@@ -2266,7 +2280,10 @@
if (activityType != ACTIVITY_TYPE_UNDEFINED) {
return activityType;
}
- return options != null ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+ if (options != null) {
+ activityType = options.getLaunchActivityType();
+ }
+ return activityType != ACTIVITY_TYPE_UNDEFINED ? activityType : ACTIVITY_TYPE_STANDARD;
}
<T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@@ -2304,7 +2321,7 @@
// Temporarily set the task id to invalid in case in re-entry.
options.setLaunchTaskId(INVALID_TASK_ID);
final TaskRecord task = anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options);
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options, onTop);
options.setLaunchTaskId(taskId);
if (task != null) {
return task.getStack();
@@ -2330,13 +2347,10 @@
}
final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
if (display != null) {
- for (int i = display.getChildCount() - 1; i >= 0; --i) {
- stack = (T) display.getChildAt(i);
- if (stack.isCompatible(windowingMode, activityType)) {
- return stack;
- }
+ stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+ if (stack != null) {
+ return stack;
}
- // TODO: We should create the stack we want on the display at this point.
}
}
@@ -2365,18 +2379,6 @@
display = getDefaultDisplay();
}
- stack = display.getOrCreateStack(windowingMode, activityType, onTop);
- if (stack != null) {
- return stack;
- }
-
- // Whatever...return some default for now.
- if (candidateTask != null && candidateTask.mBounds != null
- && mService.mSupportsFreeformWindowManagement) {
- windowingMode = WINDOWING_MODE_FREEFORM;
- } else {
- windowingMode = WINDOWING_MODE_FULLSCREEN;
- }
return display.getOrCreateStack(windowingMode, activityType, onTop);
}
@@ -2967,10 +2969,11 @@
*
* @param task The recent task to be restored.
* @param aOptions The activity options to use for restoration.
+ * @param onTop If the stack for the task should be the topmost on the display.
* @return true if the task has been restored successfully.
*/
- boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
- final ActivityStack stack = getLaunchStack(null, aOptions, task, !ON_TOP);
+ boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions, boolean onTop) {
+ final ActivityStack stack = getLaunchStack(null, aOptions, task, onTop);
final ActivityStack currentStack = task.getStack();
if (currentStack != null) {
// Task has already been restored once. See if we need to do anything more
@@ -2983,9 +2986,9 @@
currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
}
- stack.addTask(task, !ON_TOP, "restoreRecentTask");
+ stack.addTask(task, onTop, "restoreRecentTask");
// TODO: move call for creation here and other place into Stack.addTask()
- task.createWindowContainer(!ON_TOP, true /* showForAllUsers */);
+ task.createWindowContainer(onTop, true /* showForAllUsers */);
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
"Added restored task=" + task + " to stack=" + stack);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -3002,12 +3005,7 @@
@Override
public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
- if (wasTrimmed) {
- // Task was trimmed from the recent tasks list -- remove the active task record as well
- // since the user won't really be able to go back to it
- removeTaskByIdLocked(task.taskId, false /* killProcess */,
- false /* removeFromRecents */);
- }
+ // TODO: Trim active task once b/68045330 is fixed
task.removedFromRecents();
}
@@ -4567,7 +4565,7 @@
task.setTaskDockedResizing(true);
}
- final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
+ int startActivityFromRecents(int taskId, Bundle bOptions) {
final TaskRecord task;
final int callingUid;
final String callingPackage;
@@ -4582,7 +4580,7 @@
windowingMode = activityOptions.getLaunchWindowingMode();
}
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
- throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+ throw new IllegalArgumentException("startActivityFromRecents: Task "
+ taskId + " can't be launch in the home/recents stack.");
}
@@ -4600,21 +4598,12 @@
}
task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
- activityOptions);
+ activityOptions, ON_TOP);
if (task == null) {
continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
mWindowManager.executeAppTransition();
throw new IllegalArgumentException(
- "startActivityFromRecentsInner: Task " + taskId + " not found.");
- }
-
- // Since we don't have an actual source record here, we assume that the currently
- // focused activity was the source.
- final ActivityStack stack = getLaunchStack(null, activityOptions, task, ON_TOP);
-
- if (stack != null && task.getStack() != stack) {
- task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
- "startActivityFromRecents");
+ "startActivityFromRecents: Task " + taskId + " not found.");
}
// If the user must confirm credentials (e.g. when first launching a work app and the
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index a4e2e70..d42a3b9 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -94,7 +94,7 @@
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
- mService.mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
+ mService.mStackSupervisor.startActivityFromRecents(mTaskId, null);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -147,4 +147,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index 940f905..1c094c1 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -327,7 +327,9 @@
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
- getLockTaskNotify().show(false);
+ if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ getLockTaskNotify().showPinningExitToast();
+ }
try {
boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
@@ -349,10 +351,13 @@
}
/**
- * Show the lock task violation toast.
+ * Show the lock task violation toast. Currently we only show toast for screen pinning mode, and
+ * no-op if the device is in locked mode.
*/
void showLockTaskToast() {
- mHandler.post(() -> getLockTaskNotify().showToast(mLockTaskModeState));
+ if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ mHandler.post(() -> getLockTaskNotify().showEscapeToast());
+ }
}
// Starting lock task
@@ -439,7 +444,9 @@
private void performStartLockTask(String packageName, int userId, int lockTaskModeState) {
// When lock task starts, we disable the status bars.
try {
- getLockTaskNotify().show(true);
+ if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ getLockTaskNotify().showPinningStartToast();
+ }
mLockTaskModeState = lockTaskModeState;
if (getStatusBarService() != null) {
int flags = 0;
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index 0412db5..1dcb0ad 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -16,10 +16,7 @@
package com.android.server.am;
-import android.app.ActivityManager;
import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;
import android.view.WindowManager;
@@ -28,37 +25,33 @@
import com.android.internal.R;
/**
- * Helper to manage showing/hiding a image to notify them that they are entering
- * or exiting lock-to-app mode.
+ * Helper to manage showing/hiding a image to notify them that they are entering or exiting screen
+ * pinning mode. All exposed methods should be called from a handler thread.
*/
public class LockTaskNotify {
private static final String TAG = "LockTaskNotify";
private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
private final Context mContext;
- private final H mHandler;
private Toast mLastToast;
private long mLastShowToastTime;
public LockTaskNotify(Context context) {
mContext = context;
- mHandler = new H();
}
- public void showToast(int lockTaskModeState) {
- mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget();
+ /** Show "Screen pinned" toast. */
+ void showPinningStartToast() {
+ makeAllUserToastAndShow(R.string.lock_to_app_start);
}
- public void handleShowToast(int lockTaskModeState) {
- String text = null;
- if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
- text = mContext.getString(R.string.lock_to_app_toast_locked);
- } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) {
- text = mContext.getString(R.string.lock_to_app_toast);
- }
- if (text == null) {
- return;
- }
+ /** Show "Screen unpinned" toast. */
+ void showPinningExitToast() {
+ makeAllUserToastAndShow(R.string.lock_to_app_exit);
+ }
+
+ /** Show a toast that describes the gesture the user should use to escape pinned mode. */
+ void showEscapeToast() {
long showToastTime = SystemClock.elapsedRealtime();
if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
@@ -67,36 +60,15 @@
if (mLastToast != null) {
mLastToast.cancel();
}
- mLastToast = makeAllUserToastAndShow(text);
+ mLastToast = makeAllUserToastAndShow(R.string.lock_to_app_toast);
mLastShowToastTime = showToastTime;
}
- public void show(boolean starting) {
- int showString = R.string.lock_to_app_exit;
- if (starting) {
- showString = R.string.lock_to_app_start;
- }
- makeAllUserToastAndShow(mContext.getString(showString));
- }
-
- private Toast makeAllUserToastAndShow(String text) {
- Toast toast = Toast.makeText(mContext, text, Toast.LENGTH_LONG);
+ private Toast makeAllUserToastAndShow(int resId) {
+ Toast toast = Toast.makeText(mContext, resId, Toast.LENGTH_LONG);
toast.getWindowParams().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
toast.show();
return toast;
}
-
- private final class H extends Handler {
- private static final int SHOW_TOAST = 3;
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case SHOW_TOAST:
- handleShowToast(msg.arg1);
- break;
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a1b45a1..c451235 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -60,6 +60,7 @@
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.am.proto.TaskRecordProto.ACTIVITIES;
@@ -512,7 +513,7 @@
updateOverrideConfiguration(bounds);
if (!inFreeformWindowingMode()) {
// re-restore the task so it can have the proper stack association.
- mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
+ mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ab8da0c..391deb7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12443,7 +12443,8 @@
.getPrivAppDenyPermissions(pkg.packageName);
final boolean permissionViolation =
deniedPermissions == null || !deniedPermissions.contains(perm);
- if (permissionViolation) {
+ if (permissionViolation
+ && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
if (mPrivappPermissionsViolations == null) {
mPrivappPermissionsViolations = new ArraySet<>();
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 12f490f..a3585bc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -674,7 +674,8 @@
}
checked.add(activity);
- if (!s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
+ if ((activity != null)
+ && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
return false;
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ceb0ad0..6520dc9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1654,11 +1654,23 @@
mScreenshotChordVolumeDownKeyConsumed = true;
mA11yShortcutChordVolumeUpKeyConsumed = true;
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
- ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+ getAccessibilityShortcutTimeout());
}
}
}
+ private long getAccessibilityShortcutTimeout() {
+ ViewConfiguration config = ViewConfiguration.get(mContext);
+ try {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
+ ? config.getAccessibilityShortcutKeyTimeout()
+ : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
+ } catch (Settings.SettingNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private long getScreenshotChordLongPressDelay() {
if (mKeyguardDelegate.isShowing()) {
// Double the time it takes to take a screenshot from the keyguard
@@ -3866,8 +3878,7 @@
mAccessibilityTvScheduled = true;
Message msg = Message.obtain(mHandler, MSG_ACCESSIBILITY_TV);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg,
- ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+ mHandler.sendMessageDelayed(msg, getAccessibilityShortcutTimeout());
}
} else if (mAccessibilityTvScheduled) {
mHandler.removeMessages(MSG_ACCESSIBILITY_TV);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index ca3dd05..22d2bcf 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -33,12 +33,14 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StatsLogEventWrapper;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import java.util.ArrayList;
import java.util.List;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
@@ -49,6 +51,7 @@
/**
* Helper service for statsd (the native stats management service in cmds/statsd/).
* Used for registering and receiving alarms on behalf of statsd.
+ *
* @hide
*/
public class StatsCompanionService extends IStatsCompanionService.Stub {
@@ -90,7 +93,7 @@
// Needed since the new user basically has a version of every app.
informAllUidsLocked(context);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+ Slog.e(TAG, "Failed to inform statsd latest update of all apps", e);
forgetEverything();
}
}
@@ -99,9 +102,9 @@
Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
}
- private final static int[] toIntArray(List<Integer> list){
+ private final static int[] toIntArray(List<Integer> list) {
int[] ret = new int[list.size()];
- for(int i = 0;i < ret.length;i++) {
+ for (int i = 0; i < ret.length; i++) {
ret[i] = list.get(i);
}
return ret;
@@ -113,7 +116,7 @@
PackageManager pm = context.getPackageManager();
final List<UserInfo> users = um.getUsers(true);
if (DEBUG) {
- Slog.w(TAG, "Iterating over "+users.size() + " profiles.");
+ Slog.w(TAG, "Iterating over " + users.size() + " profiles.");
}
List<Integer> uids = new ArrayList();
@@ -122,23 +125,23 @@
// Add in all the apps for every user/profile.
for (UserInfo profile : users) {
- List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
- for (int j = 0; j < pi.size(); j++) {
- if (pi.get(j).applicationInfo != null) {
- uids.add(pi.get(j).applicationInfo.uid);
- versions.add(pi.get(j).versionCode);
- apps.add(pi.get(j).packageName);
- }
- }
+ List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
+ for (int j = 0; j < pi.size(); j++) {
+ if (pi.get(j).applicationInfo != null) {
+ uids.add(pi.get(j).applicationInfo.uid);
+ versions.add(pi.get(j).versionCode);
+ apps.add(pi.get(j).packageName);
+ }
+ }
}
sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new
- String[apps.size()]));
+ String[apps.size()]));
if (DEBUG) {
- Slog.w(TAG, "Sent data for "+uids.size() +" apps");
+ Slog.w(TAG, "Sent data for " + uids.size() + " apps");
}
}
- public final static class AppUpdateReceiver extends BroadcastReceiver {
+ public final static class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
@@ -147,7 +150,7 @@
* waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
*/
if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
return; // Keep only replacing or normal add and remove.
}
synchronized (sStatsdLock) {
@@ -180,9 +183,9 @@
}
}
}
- };
+ }
- public 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.");
@@ -200,7 +203,7 @@
}
// AlarmManager releases its own wakelock here.
}
- };
+ }
public final static class PollingAlarmReceiver extends BroadcastReceiver {
@Override
@@ -220,7 +223,7 @@
}
// AlarmManager releases its own wakelock here.
}
- };
+ }
@Override // Binder call
public void setAnomalyAlarm(long timestampMs) {
@@ -286,33 +289,34 @@
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
@Override // Binder call
- public String pullData(int pullCode) {
+ public StatsLogEventWrapper[] pullData(int pullCode) {
enforceCallingPermission();
- if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+ if (DEBUG) {
+ Slog.d(TAG, "Pulling " + pullCode);
+ }
- StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+ List<StatsLogEventWrapper> ret = new ArrayList<>();
switch (pullCode) {
- case PULL_CODE_KERNEL_WAKELOCKS:
+ case PULL_CODE_KERNEL_WAKELOCKS: {
final KernelWakelockStats wakelockStats =
mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-
for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
String name = ent.getKey();
KernelWakelockStats.Entry kws = ent.getValue();
- s.append("Wakelock ")
- .append(name)
- .append(", time=")
- .append(kws.mTotalTime)
- .append(", count=")
- .append(kws.mCount)
- .append('\n');
+ StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+ e.writeInt(kws.mCount);
+ e.writeInt(kws.mVersion);
+ e.writeLong(kws.mTotalTime);
+ e.writeString(name);
+ ret.add(e);
}
break;
+ }
default:
Slog.w(TAG, "No such pollable data as " + pullCode);
return null;
}
- return s.toString();
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
}
@Override // Binder call
@@ -331,7 +335,9 @@
// Lifecycle and related code
- /** Fetches the statsd IBinder service */
+ /**
+ * Fetches the statsd IBinder service
+ */
private static IStatsManager fetchStatsdService() {
return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
}
@@ -363,13 +369,17 @@
}
}
- /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+ /**
+ * Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
+ */
private void systemReady() {
if (DEBUG) Slog.d(TAG, "Learned that systemReady");
sayHiToStatsd();
}
- /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+ /**
+ * Tells statsd that statscompanion is ready. If the binder call returns, link to statsd.
+ */
private void sayHiToStatsd() {
synchronized (sStatsdLock) {
if (sStatsd != null) {
@@ -398,14 +408,14 @@
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null,
- null);
+ null);
// Setup receiver for user initialize (which happens once for a new user) and
// if a user is removed.
filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
filter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
- filter, null, null);
+ filter, null, null);
// Pull the latest state of UID->app name, version mapping when statsd starts.
informAllUidsLocked(mContext);
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 5365e27..2ef7f25 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -295,13 +295,11 @@
void updateThumbnailLayer() {
if (thumbnail != null) {
final int layer = mAppToken.getHighestAnimLayer();
- if (layer != mThumbnailLayer) {
- if (DEBUG_LAYERS) Slog.v(TAG,
- "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
- thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
- - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
- mThumbnailLayer = layer;
- }
+ if (DEBUG_LAYERS) Slog.v(TAG,
+ "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
+ thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+ - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+ mThumbnailLayer = layer;
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 5bfea98..a4ab3ba 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -28,6 +28,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
import static com.android.server.wm.proto.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.proto.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
@@ -37,6 +38,7 @@
import android.content.res.Configuration;
import android.util.proto.ProtoOutputStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
/**
@@ -342,6 +344,26 @@
protoOutputStream.end(token);
}
+ /**
+ * Dumps the names of this container children in the input print writer indenting each
+ * level with the input prefix.
+ */
+ public void dumpChildrenNames(PrintWriter pw, String prefix) {
+ final String childPrefix = prefix + " ";
+ pw.println(getName()
+ + " type=" + activityTypeToString(getActivityType())
+ + " mode=" + windowingModeToString(getWindowingMode()));
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final E cc = getChildAt(i);
+ pw.print(childPrefix + "#" + i + " ");
+ cc.dumpChildrenNames(pw, childPrefix);
+ }
+ }
+
+ String getName() {
+ return toString();
+ }
+
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1b0825e..563eb9c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -674,20 +674,6 @@
}
/**
- * Dumps the names of this container children in the input print writer indenting each
- * level with the input prefix.
- */
- void dumpChildrenNames(StringBuilder out, String prefix) {
- final String childPrefix = prefix + " ";
- out.append(getName() + "\n");
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- out.append(childPrefix + "#" + i + " ");
- wc.dumpChildrenNames(out, childPrefix);
- }
- }
-
- /**
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.proto.WindowContainerProto}.
*
@@ -704,10 +690,6 @@
protoOutputStream.end(token);
}
- String getName() {
- return toString();
- }
-
private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire();
if (wrapper == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b133bd4..f0da474 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6921,9 +6921,7 @@
return;
} else if ("containers".equals(cmd)) {
synchronized(mWindowMap) {
- StringBuilder output = new StringBuilder();
- mRoot.dumpChildrenNames(output, " ");
- pw.println(output.toString());
+ mRoot.dumpChildrenNames(pw, " ");
pw.println(" ");
mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
}
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 9a17635..3eaf488 100644
--- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -103,8 +103,8 @@
// fd2 A file descriptor bound to the following netlink groups
// (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
base::unique_fd
- fd1(conntrackSocket(NFNLGRP_CONNTRACK_NEW | NFNLGRP_CONNTRACK_DESTROY)),
- fd2(conntrackSocket(NFNLGRP_CONNTRACK_UPDATE | NFNLGRP_CONNTRACK_DESTROY));
+ fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
+ fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
if (fd1.get() < 0 || fd2.get() < 0) {
ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
return false;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b950191..d9db22e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -206,7 +206,8 @@
"com.android.server.autofill.AutofillManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
"com.android.server.timezone.RulesManagerService$Lifecycle";
-
+ private static final String IOT_SERVICE_CLASS =
+ "com.google.android.things.services.IoTSystemService";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
@@ -1544,10 +1545,11 @@
traceEnd();
}
- // Statsd helper
- traceBeginAndSlog("StartStatsCompanionService");
- mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
- traceEnd();
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+ traceBeginAndSlog("StartIoTSystemService");
+ mSystemServiceManager.startService(IOT_SERVICE_CLASS);
+ traceEnd();
+ }
// Before things start rolling, be sure we have decided whether
// we are in safe mode.
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 190b3a6..5c2b66f 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -33,7 +33,7 @@
import android.net.apf.ApfGenerator;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.IpConnectivityLog;
@@ -238,7 +238,7 @@
private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
private final ApfCapabilities mApfCapabilities;
- private final IpManager.Callback mIpManagerCallback;
+ private final IpClient.Callback mIpClientCallback;
private final NetworkInterface mNetworkInterface;
private final IpConnectivityLog mMetricsLog;
@@ -262,10 +262,10 @@
@VisibleForTesting
ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
- IpManager.Callback ipManagerCallback, boolean multicastFilter,
+ IpClient.Callback ipClientCallback, boolean multicastFilter,
boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
mApfCapabilities = apfCapabilities;
- mIpManagerCallback = ipManagerCallback;
+ mIpClientCallback = ipClientCallback;
mNetworkInterface = networkInterface;
mMulticastFilter = multicastFilter;
mDrop802_3Frames = ieee802_3Filter;
@@ -275,7 +275,7 @@
mMetricsLog = log;
- // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
+ // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
maybeStartFilter();
}
@@ -1051,7 +1051,7 @@
if (VDBG) {
hexDump("Installing filter: ", program, program.length);
}
- mIpManagerCallback.installPacketFilter(program);
+ mIpClientCallback.installPacketFilter(program);
logApfProgramEventLocked(now);
mLastInstallEvent = new ApfProgramEvent();
mLastInstallEvent.lifetime = programMinLifetime;
@@ -1161,7 +1161,7 @@
* filtering using APF programs.
*/
public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
- NetworkInterface networkInterface, IpManager.Callback ipManagerCallback,
+ NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
if (apfCapabilities == null || networkInterface == null) return null;
if (apfCapabilities.apfVersionSupported == 0) return null;
@@ -1178,7 +1178,7 @@
Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
return null;
}
- return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback,
+ return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
}
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
new file mode 100644
index 0000000..2359fab
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -0,0 +1,1712 @@
+/*
+ * 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.net.ip;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties.ProvisioningChange;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.IState;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+ private static final boolean DBG = false;
+
+ // For message logging.
+ private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+ private static final SparseArray<String> sWhatToString =
+ MessageUtils.findMessageNames(sMessageClasses);
+
+ /**
+ * Callbacks for handling IpClient events.
+ */
+ public static class Callback {
+ // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
+ // when constructing a ProvisioningConfiguration.
+ //
+ // Implementations of onPreDhcpAction() must call
+ // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
+ // to proceed.
+ public void onPreDhcpAction() {}
+ public void onPostDhcpAction() {}
+
+ // This is purely advisory and not an indication of provisioning
+ // success or failure. This is only here for callers that want to
+ // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+ // DHCPv4 or static IPv4 configuration failure or success can be
+ // determined by whether or not the passed-in DhcpResults object is
+ // null or not.
+ public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+ public void onProvisioningSuccess(LinkProperties newLp) {}
+ public void onProvisioningFailure(LinkProperties newLp) {}
+
+ // Invoked on LinkProperties changes.
+ public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+ // Called when the internal IpReachabilityMonitor (if enabled) has
+ // detected the loss of a critical number of required neighbors.
+ public void onReachabilityLost(String logMsg) {}
+
+ // Called when the IpClient state machine terminates.
+ public void onQuit() {}
+
+ // Install an APF program to filter incoming packets.
+ public void installPacketFilter(byte[] filter) {}
+
+ // If multicast filtering cannot be accomplished with APF, this function will be called to
+ // actuate multicast filtering using another means.
+ public void setFallbackMulticastFilter(boolean enabled) {}
+
+ // Enabled/disable Neighbor Discover offload functionality. This is
+ // called, for example, whenever 464xlat is being started or stopped.
+ public void setNeighborDiscoveryOffload(boolean enable) {}
+ }
+
+ // Use a wrapper class to log in order to ensure complete and detailed
+ // logging. This method is lighter weight than annotations/reflection
+ // and has the following benefits:
+ //
+ // - No invoked method can be forgotten.
+ // Any new method added to IpClient.Callback must be overridden
+ // here or it will never be called.
+ //
+ // - No invoking call site can be forgotten.
+ // Centralized logging in this way means call sites don't need to
+ // remember to log, and therefore no call site can be forgotten.
+ //
+ // - No variation in log format among call sites.
+ // Encourages logging of any available arguments, and all call sites
+ // are necessarily logged identically.
+ //
+ // TODO: Find an lighter weight approach.
+ private class LoggingCallbackWrapper extends Callback {
+ private static final String PREFIX = "INVOKE ";
+ private Callback mCallback;
+
+ public LoggingCallbackWrapper(Callback callback) {
+ mCallback = callback;
+ }
+
+ private void log(String msg) {
+ mLog.log(PREFIX + msg);
+ }
+
+ @Override
+ public void onPreDhcpAction() {
+ mCallback.onPreDhcpAction();
+ log("onPreDhcpAction()");
+ }
+ @Override
+ public void onPostDhcpAction() {
+ mCallback.onPostDhcpAction();
+ log("onPostDhcpAction()");
+ }
+ @Override
+ public void onNewDhcpResults(DhcpResults dhcpResults) {
+ mCallback.onNewDhcpResults(dhcpResults);
+ log("onNewDhcpResults({" + dhcpResults + "})");
+ }
+ @Override
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ mCallback.onProvisioningSuccess(newLp);
+ log("onProvisioningSuccess({" + newLp + "})");
+ }
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ mCallback.onProvisioningFailure(newLp);
+ log("onProvisioningFailure({" + newLp + "})");
+ }
+ @Override
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ mCallback.onLinkPropertiesChange(newLp);
+ log("onLinkPropertiesChange({" + newLp + "})");
+ }
+ @Override
+ public void onReachabilityLost(String logMsg) {
+ mCallback.onReachabilityLost(logMsg);
+ log("onReachabilityLost(" + logMsg + ")");
+ }
+ @Override
+ public void onQuit() {
+ mCallback.onQuit();
+ log("onQuit()");
+ }
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mCallback.installPacketFilter(filter);
+ log("installPacketFilter(byte[" + filter.length + "])");
+ }
+ @Override
+ public void setFallbackMulticastFilter(boolean enabled) {
+ mCallback.setFallbackMulticastFilter(enabled);
+ log("setFallbackMulticastFilter(" + enabled + ")");
+ }
+ @Override
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ mCallback.setNeighborDiscoveryOffload(enable);
+ log("setNeighborDiscoveryOffload(" + enable + ")");
+ }
+ }
+
+ /**
+ * This class encapsulates parameters to be passed to
+ * IpClient#startProvisioning(). A defensive copy is made by IpClient
+ * and the values specified herein are in force until IpClient#stop()
+ * is called.
+ *
+ * Example use:
+ *
+ * final ProvisioningConfiguration config =
+ * mIpClient.buildProvisioningConfiguration()
+ * .withPreDhcpAction()
+ * .withProvisioningTimeoutMs(36 * 1000)
+ * .build();
+ * mIpClient.startProvisioning(config);
+ * ...
+ * mIpClient.stop();
+ *
+ * The specified provisioning configuration will only be active until
+ * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
+ * must specify the configuration again.
+ */
+ public static class ProvisioningConfiguration {
+ // TODO: Delete this default timeout once those callers that care are
+ // fixed to pass in their preferred timeout.
+ //
+ // We pick 36 seconds so we can send DHCP requests at
+ //
+ // t=0, t=2, t=6, t=14, t=30
+ //
+ // allowing for 10% jitter.
+ private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+ public static class Builder {
+ private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
+ public Builder withoutIpReachabilityMonitor() {
+ mConfig.mUsingIpReachabilityMonitor = false;
+ return this;
+ }
+
+ public Builder withPreDhcpAction() {
+ mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+ return this;
+ }
+
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+ return this;
+ }
+
+ public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+ mConfig.mInitialConfig = initialConfig;
+ return this;
+ }
+
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ mConfig.mStaticIpConfig = staticConfig;
+ return this;
+ }
+
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ mConfig.mApfCapabilities = apfCapabilities;
+ return this;
+ }
+
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ mConfig.mProvisioningTimeoutMs = timeoutMs;
+ return this;
+ }
+
+ public Builder withIPv6AddrGenModeEUI64() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+ return this;
+ }
+
+ public Builder withIPv6AddrGenModeStablePrivacy() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ return this;
+ }
+
+ public Builder withNetwork(Network network) {
+ mConfig.mNetwork = network;
+ return this;
+ }
+
+ public Builder withDisplayName(String displayName) {
+ mConfig.mDisplayName = displayName;
+ return this;
+ }
+
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(mConfig);
+ }
+ }
+
+ /* package */ boolean mEnableIPv4 = true;
+ /* package */ boolean mEnableIPv6 = true;
+ /* package */ boolean mUsingIpReachabilityMonitor = true;
+ /* package */ int mRequestedPreDhcpActionMs;
+ /* package */ InitialConfiguration mInitialConfig;
+ /* package */ StaticIpConfiguration mStaticIpConfig;
+ /* package */ ApfCapabilities mApfCapabilities;
+ /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+ /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ /* package */ Network mNetwork = null;
+ /* package */ String mDisplayName = null;
+
+ public ProvisioningConfiguration() {} // used by Builder
+
+ public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
+ mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+ mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+ mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+ mStaticIpConfig = other.mStaticIpConfig;
+ mApfCapabilities = other.mApfCapabilities;
+ mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+ mNetwork = other.mNetwork;
+ mDisplayName = other.mDisplayName;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
+ .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+ .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+ .add("mInitialConfig: " + mInitialConfig)
+ .add("mStaticIpConfig: " + mStaticIpConfig)
+ .add("mApfCapabilities: " + mApfCapabilities)
+ .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+ .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+ .add("mNetwork: " + mNetwork)
+ .add("mDisplayName: " + mDisplayName)
+ .toString();
+ }
+
+ public boolean isValid() {
+ return (mInitialConfig == null) || mInitialConfig.isValid();
+ }
+ }
+
+ public static class InitialConfiguration {
+ public final Set<LinkAddress> ipAddresses = new HashSet<>();
+ public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+ public final Set<InetAddress> dnsServers = new HashSet<>();
+ public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+
+ public static InitialConfiguration copy(InitialConfiguration config) {
+ if (config == null) {
+ return null;
+ }
+ InitialConfiguration configCopy = new InitialConfiguration();
+ configCopy.ipAddresses.addAll(config.ipAddresses);
+ configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+ configCopy.dnsServers.addAll(config.dnsServers);
+ return configCopy;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
+ join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+ join(", ", dnsServers), gateway);
+ }
+
+ public boolean isValid() {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ // For every IP address, there must be at least one prefix containing that address.
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+ return false;
+ }
+ }
+ // For every dns server, there must be at least one prefix containing that address.
+ for (InetAddress addr : dnsServers) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+ return false;
+ }
+ }
+ // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+ // (read: compliant with RFC4291#section2.5.4).
+ if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // If directlyConnectedRoutes contains an IPv6 default route
+ // then ipAddresses MUST contain at least one non-ULA GUA.
+ if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+ && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+ return false;
+ }
+ // The prefix length of routes in directlyConnectedRoutes be within reasonable
+ // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+ if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // There no more than one IPv4 address
+ if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return true if the given list of addressess and routes satisfies provisioning for this
+ * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+ * because addresses and routes seen by Netlink will contain additional fields like flags,
+ * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+ * provisioning check always fails.
+ *
+ * If the given list of routes is null, only addresses are taken into considerations.
+ */
+ public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+ return false;
+ }
+ }
+
+ if (routes != null) {
+ for (IpPrefix prefix : directlyConnectedRoutes) {
+ if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+ return !route.hasGateway() && prefix.equals(route.getDestination());
+ }
+
+ private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+ return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+ }
+
+ private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+ return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+ }
+
+ private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+ return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+ && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
+ }
+
+ private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+ return prefix.getAddress().equals(Inet6Address.ANY);
+ }
+
+ private static boolean isIPv6GUA(LinkAddress addr) {
+ return addr.isIPv6() && addr.isGlobalPreferred();
+ }
+ }
+
+ public static final String DUMP_ARG = "ipclient";
+ public static final String DUMP_ARG_CONFIRM = "confirm";
+
+ private static final int CMD_TERMINATE_AFTER_STOP = 1;
+ private static final int CMD_STOP = 2;
+ private static final int CMD_START = 3;
+ private static final int CMD_CONFIRM = 4;
+ private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
+ // Sent by NetlinkTracker to communicate netlink events.
+ private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+ private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
+ private static final int CMD_UPDATE_HTTP_PROXY = 8;
+ private static final int CMD_SET_MULTICAST_FILTER = 9;
+ private static final int EVENT_PROVISIONING_TIMEOUT = 10;
+ private static final int EVENT_DHCPACTION_TIMEOUT = 11;
+
+ private static final int MAX_LOG_RECORDS = 500;
+ private static final int MAX_PACKET_RECORDS = 100;
+
+ private static final boolean NO_CALLBACKS = false;
+ private static final boolean SEND_CALLBACKS = true;
+
+ // This must match the interface prefix in clatd.c.
+ // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+ private static final String CLAT_PREFIX = "v4-";
+
+ private final State mStoppedState = new StoppedState();
+ private final State mStoppingState = new StoppingState();
+ private final State mStartedState = new StartedState();
+ private final State mRunningState = new RunningState();
+
+ private final String mTag;
+ private final Context mContext;
+ private final String mInterfaceName;
+ private final String mClatInterfaceName;
+ @VisibleForTesting
+ protected final Callback mCallback;
+ private final INetworkManagementService mNwService;
+ private final NetlinkTracker mNetlinkTracker;
+ private final WakeupMessage mProvisioningTimeoutAlarm;
+ private final WakeupMessage mDhcpActionTimeoutAlarm;
+ private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+ private final SharedLog mLog;
+ private final LocalLog mConnectivityPacketLog;
+ private final MessageHandlingLogger mMsgStateLogger;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ private final InterfaceController mInterfaceCtrl;
+
+ private NetworkInterface mNetworkInterface;
+
+ /**
+ * Non-final member variables accessed only from within our StateMachine.
+ */
+ private LinkProperties mLinkProperties;
+ private ProvisioningConfiguration mConfiguration;
+ private IpReachabilityMonitor mIpReachabilityMonitor;
+ private DhcpClient mDhcpClient;
+ private DhcpResults mDhcpResults;
+ private String mTcpBufferSizes;
+ private ProxyInfo mHttpProxy;
+ private ApfFilter mApfFilter;
+ private boolean mMulticastFiltering;
+ private long mStartTimeMillis;
+
+ public IpClient(Context context, String ifName, Callback callback) {
+ this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
+ NetdService.getInstance());
+ }
+
+ /**
+ * An expanded constructor, useful for dependency injection.
+ * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+ */
+ public IpClient(Context context, String ifName, Callback callback,
+ INetworkManagementService nwService) {
+ this(context, ifName, callback, nwService, NetdService.getInstance());
+ }
+
+ @VisibleForTesting
+ IpClient(Context context, String ifName, Callback callback,
+ INetworkManagementService nwService, INetd netd) {
+ super(IpClient.class.getSimpleName() + "." + ifName);
+ mTag = getName();
+
+ mContext = context;
+ mInterfaceName = ifName;
+ mClatInterfaceName = CLAT_PREFIX + ifName;
+ mCallback = new LoggingCallbackWrapper(callback);
+ mNwService = nwService;
+
+ mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
+ mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
+ mMsgStateLogger = new MessageHandlingLogger();
+
+ mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
+
+ mNetlinkTracker = new NetlinkTracker(
+ mInterfaceName,
+ new NetlinkTracker.Callback() {
+ @Override
+ public void update() {
+ sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ }
+ }) {
+ @Override
+ public void interfaceAdded(String iface) {
+ super.interfaceAdded(iface);
+ if (mClatInterfaceName.equals(iface)) {
+ mCallback.setNeighborDiscoveryOffload(false);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceAdded(" + iface +")";
+ logMsg(msg);
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ super.interfaceRemoved(iface);
+ // TODO: Also observe mInterfaceName going down and take some
+ // kind of appropriate action.
+ if (mClatInterfaceName.equals(iface)) {
+ // TODO: consider sending a message to the IpClient main
+ // StateMachine thread, in case "NDO enabled" state becomes
+ // tied to more things that 464xlat operation.
+ mCallback.setNeighborDiscoveryOffload(true);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceRemoved(" + iface +")";
+ logMsg(msg);
+ }
+
+ private void logMsg(String msg) {
+ Log.d(mTag, msg);
+ getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
+ }
+ };
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
+ mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
+ () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
+
+ mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+ mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+ // Anything the StateMachine may access must have been instantiated
+ // before this point.
+ configureAndStartStateMachine();
+
+ // Anything that may send messages to the StateMachine must only be
+ // configured to do so after the StateMachine has started (above).
+ startStateMachineUpdaters();
+ }
+
+ private void configureAndStartStateMachine() {
+ addState(mStoppedState);
+ addState(mStartedState);
+ addState(mRunningState, mStartedState);
+ addState(mStoppingState);
+
+ setInitialState(mStoppedState);
+
+ super.start();
+ }
+
+ private void startStateMachineUpdaters() {
+ try {
+ mNwService.registerObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't register NetlinkTracker: %s", e);
+ }
+
+ mMultinetworkPolicyTracker.start();
+ }
+
+ private void stopStateMachineUpdaters() {
+ try {
+ mNwService.unregisterObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't unregister NetlinkTracker: %s", e);
+ }
+
+ mMultinetworkPolicyTracker.shutdown();
+ }
+
+ @Override
+ protected void onQuitting() {
+ mCallback.onQuit();
+ }
+
+ // Shut down this IpClient instance altogether.
+ public void shutdown() {
+ stop();
+ sendMessage(CMD_TERMINATE_AFTER_STOP);
+ }
+
+ public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+ return new ProvisioningConfiguration.Builder();
+ }
+
+ public void startProvisioning(ProvisioningConfiguration req) {
+ if (!req.isValid()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ return;
+ }
+
+ getNetworkInterface();
+
+ mCallback.setNeighborDiscoveryOffload(true);
+ sendMessage(CMD_START, new ProvisioningConfiguration(req));
+ }
+
+ // TODO: Delete this.
+ public void startProvisioning(StaticIpConfiguration staticIpConfig) {
+ startProvisioning(buildProvisioningConfiguration()
+ .withStaticConfiguration(staticIpConfig)
+ .build());
+ }
+
+ public void startProvisioning() {
+ startProvisioning(new ProvisioningConfiguration());
+ }
+
+ public void stop() {
+ sendMessage(CMD_STOP);
+ }
+
+ public void confirmConfiguration() {
+ sendMessage(CMD_CONFIRM);
+ }
+
+ public void completedPreDhcpAction() {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+
+ /**
+ * Set the TCP buffer sizes to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+ }
+
+ /**
+ * Set the HTTP Proxy configuration to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setHttpProxy(ProxyInfo proxyInfo) {
+ sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+ }
+
+ /**
+ * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
+ * if not, Callback.setFallbackMulticastFilter() is called.
+ */
+ public void setMulticastFilter(boolean enabled) {
+ sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+ // Execute confirmConfiguration() and take no further action.
+ confirmConfiguration();
+ return;
+ }
+
+ // Thread-unsafe access to mApfFilter but just used for debugging.
+ final ApfFilter apfFilter = mApfFilter;
+ final ProvisioningConfiguration provisioningConfig = mConfiguration;
+ final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+ ? provisioningConfig.mApfCapabilities : null;
+
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println(mTag + " APF dump:");
+ pw.increaseIndent();
+ if (apfFilter != null) {
+ apfFilter.dump(pw);
+ } else {
+ pw.print("No active ApfFilter; ");
+ if (provisioningConfig == null) {
+ pw.println("IpClient not yet started.");
+ } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+ pw.println("Hardware does not support APF.");
+ } else {
+ pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+ }
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " current ProvisioningConfiguration:");
+ pw.increaseIndent();
+ pw.println(Objects.toString(provisioningConfig, "N/A"));
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " StateMachine dump:");
+ pw.increaseIndent();
+ mLog.dump(fd, pw, args);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " connectivity packet log:");
+ pw.println();
+ pw.println("Debug with python and scapy via:");
+ pw.println("shell$ python");
+ pw.println(">>> from scapy import all as scapy");
+ pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+ pw.println();
+
+ pw.increaseIndent();
+ mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+
+
+ /**
+ * Internals.
+ */
+
+ @Override
+ protected String getWhatToString(int what) {
+ return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+ }
+
+ @Override
+ protected String getLogRecString(Message msg) {
+ final String logLine = String.format(
+ "%s/%d %d %d %s [%s]",
+ mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
+ msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+ final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+ mLog.log(richerLogLine);
+ if (DBG) {
+ Log.d(mTag, richerLogLine);
+ }
+
+ mMsgStateLogger.reset();
+ return logLine;
+ }
+
+ @Override
+ protected boolean recordLogRec(Message msg) {
+ // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+ // and we already log any LinkProperties change that results in an
+ // invocation of IpClient.Callback#onLinkPropertiesChange().
+ final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ if (!shouldLog) {
+ mMsgStateLogger.reset();
+ }
+ return shouldLog;
+ }
+
+ private void logError(String fmt, Object... args) {
+ final String msg = "ERROR " + String.format(fmt, args);
+ Log.e(mTag, msg);
+ mLog.log(msg);
+ }
+
+ private void getNetworkInterface() {
+ try {
+ mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
+ } catch (SocketException | NullPointerException e) {
+ // TODO: throw new IllegalStateException.
+ logError("Failed to get interface object: %s", e);
+ }
+ }
+
+ // This needs to be called with care to ensure that our LinkProperties
+ // are in sync with the actual LinkProperties of the interface. For example,
+ // we should only call this if we know for sure that there are no IP addresses
+ // assigned to the interface, etc.
+ private void resetLinkProperties() {
+ mNetlinkTracker.clearLinkProperties();
+ mConfiguration = null;
+ mDhcpResults = null;
+ mTcpBufferSizes = "";
+ mHttpProxy = null;
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+ }
+
+ private void recordMetric(final int type) {
+ if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
+ final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
+ mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+ }
+
+ // For now: use WifiStateMachine's historical notion of provisioned.
+ @VisibleForTesting
+ static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+ // For historical reasons, we should connect even if all we have is
+ // an IPv4 address and nothing else.
+ if (lp.hasIPv4Address() || lp.isProvisioned()) {
+ return true;
+ }
+ if (config == null) {
+ return false;
+ }
+
+ // When an InitialConfiguration is specified, ignore any difference with previous
+ // properties and instead check if properties observed match the desired properties.
+ return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+ }
+
+ // TODO: Investigate folding all this into the existing static function
+ // LinkProperties.compareProvisioning() or some other single function that
+ // takes two LinkProperties objects and returns a ProvisioningChange
+ // object that is a correct and complete assessment of what changed, taking
+ // account of the asymmetries described in the comments in this function.
+ // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+ private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+ ProvisioningChange delta;
+ InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+ final boolean wasProvisioned = isProvisioned(oldLp, config);
+ final boolean isProvisioned = isProvisioned(newLp, config);
+
+ if (!wasProvisioned && isProvisioned) {
+ delta = ProvisioningChange.GAINED_PROVISIONING;
+ } else if (wasProvisioned && isProvisioned) {
+ delta = ProvisioningChange.STILL_PROVISIONED;
+ } else if (!wasProvisioned && !isProvisioned) {
+ delta = ProvisioningChange.STILL_NOT_PROVISIONED;
+ } else {
+ // (wasProvisioned && !isProvisioned)
+ //
+ // Note that this is true even if we lose a configuration element
+ // (e.g., a default gateway) that would not be required to advance
+ // into provisioned state. This is intended: if we have a default
+ // router and we lose it, that's a sure sign of a problem, but if
+ // we connect to a network with no IPv4 DNS servers, we consider
+ // that to be a network without DNS servers and connect anyway.
+ //
+ // See the comment below.
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+ final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+ final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+ // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+ // provisioning. Otherwise, when a hotspot that loses Internet
+ // access sends out a 0-lifetime RA to its clients, the clients
+ // will disconnect and then reconnect, avoiding the bad hotspot,
+ // instead of getting stuck on the bad hotspot. http://b/31827713 .
+ //
+ // This is incorrect because if the hotspot then regains Internet
+ // access with a different prefix, TCP connections on the
+ // deprecated addresses will remain stuck.
+ //
+ // Note that we can still be disconnected by IpReachabilityMonitor
+ // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+ // accompanying code in IpReachabilityMonitor) is unreachable.
+ final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
+
+ // Additionally:
+ //
+ // Partial configurations (e.g., only an IPv4 address with no DNS
+ // servers and no default route) are accepted as long as DHCPv4
+ // succeeds. On such a network, isProvisioned() will always return
+ // false, because the configuration is not complete, but we want to
+ // connect anyway. It might be a disconnected network such as a
+ // Chromecast or a wireless printer, for example.
+ //
+ // Because on such a network isProvisioned() will always return false,
+ // delta will never be LOST_PROVISIONING. So check for loss of
+ // provisioning here too.
+ if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ // Additionally:
+ //
+ // If the previous link properties had a global IPv6 address and an
+ // IPv6 default route then also consider the loss of that default route
+ // to be a loss of provisioning. See b/27962810.
+ if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ return delta;
+ }
+
+ private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
+ switch (delta) {
+ case GAINED_PROVISIONING:
+ if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
+ recordMetric(IpManagerEvent.PROVISIONING_OK);
+ mCallback.onProvisioningSuccess(newLp);
+ break;
+
+ case LOST_PROVISIONING:
+ if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
+ recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+ mCallback.onProvisioningFailure(newLp);
+ break;
+
+ default:
+ if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
+ mCallback.onLinkPropertiesChange(newLp);
+ break;
+ }
+ }
+
+ // Updates all IpClient-related state concerned with LinkProperties.
+ // Returns a ProvisioningChange for possibly notifying other interested
+ // parties that are not fronted by IpClient.
+ private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+ if (mApfFilter != null) {
+ mApfFilter.setLinkProperties(newLp);
+ }
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.updateLinkProperties(newLp);
+ }
+
+ ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
+ mLinkProperties = new LinkProperties(newLp);
+
+ if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+ // TODO: Add a proper ProvisionedState and cancel the alarm in
+ // its enter() method.
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ return delta;
+ }
+
+ private LinkProperties assembleLinkProperties() {
+ // [1] Create a new LinkProperties object to populate.
+ LinkProperties newLp = new LinkProperties();
+ newLp.setInterfaceName(mInterfaceName);
+
+ // [2] Pull in data from netlink:
+ // - IPv4 addresses
+ // - IPv6 addresses
+ // - IPv6 routes
+ // - IPv6 DNS servers
+ //
+ // N.B.: this is fundamentally race-prone and should be fixed by
+ // changing NetlinkTracker from a hybrid edge/level model to an
+ // edge-only model, or by giving IpClient its own netlink socket(s)
+ // so as to track all required information directly.
+ LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+ newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+ for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+ // [3] Add in data from DHCPv4, if available.
+ //
+ // mDhcpResults is never shared with any other owner so we don't have
+ // to worry about concurrent modification.
+ if (mDhcpResults != null) {
+ for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+ newLp.setDomains(mDhcpResults.domains);
+
+ if (mDhcpResults.mtu != 0) {
+ newLp.setMtu(mDhcpResults.mtu);
+ }
+ }
+
+ // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+ if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+ newLp.setTcpBufferSizes(mTcpBufferSizes);
+ }
+ if (mHttpProxy != null) {
+ newLp.setHttpProxy(mHttpProxy);
+ }
+
+ // [5] Add data from InitialConfiguration
+ if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+ InitialConfiguration config = mConfiguration.mInitialConfig;
+ // Add InitialConfiguration routes and dns server addresses once all addresses
+ // specified in the InitialConfiguration have been observed with Netlink.
+ if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+ for (IpPrefix prefix : config.directlyConnectedRoutes) {
+ newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+ }
+ }
+ addAllReachableDnsServers(newLp, config.dnsServers);
+ }
+ final LinkProperties oldLp = mLinkProperties;
+ if (DBG) {
+ Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+ netlinkLinkProperties, newLp, oldLp));
+ }
+
+ // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+ // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+ return newLp;
+ }
+
+ private static void addAllReachableDnsServers(
+ LinkProperties lp, Iterable<InetAddress> dnses) {
+ // TODO: Investigate deleting this reachability check. We should be
+ // able to pass everything down to netd and let netd do evaluation
+ // and RFC6724-style sorting.
+ for (InetAddress dns : dnses) {
+ if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+ lp.addDnsServer(dns);
+ }
+ }
+ }
+
+ // Returns false if we have lost provisioning, true otherwise.
+ private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+ final LinkProperties newLp = assembleLinkProperties();
+ if (Objects.equals(newLp, mLinkProperties)) {
+ return true;
+ }
+ final ProvisioningChange delta = setLinkProperties(newLp);
+ if (sendCallbacks) {
+ dispatchCallback(delta, newLp);
+ }
+ return (delta != ProvisioningChange.LOST_PROVISIONING);
+ }
+
+ private void handleIPv4Success(DhcpResults dhcpResults) {
+ mDhcpResults = new DhcpResults(dhcpResults);
+ final LinkProperties newLp = assembleLinkProperties();
+ final ProvisioningChange delta = setLinkProperties(newLp);
+
+ if (DBG) {
+ Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+ }
+ mCallback.onNewDhcpResults(dhcpResults);
+ dispatchCallback(delta, newLp);
+ }
+
+ private void handleIPv4Failure() {
+ // TODO: Investigate deleting this clearIPv4Address() call.
+ //
+ // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+ // that could trigger a call to this function. If we missed handling
+ // that message in StartedState for some reason we would still clear
+ // any addresses upon entry to StoppedState.
+ mInterfaceCtrl.clearIPv4Address();
+ mDhcpResults = null;
+ if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+ mCallback.onNewDhcpResults(null);
+
+ handleProvisioningFailure();
+ }
+
+ private void handleProvisioningFailure() {
+ final LinkProperties newLp = assembleLinkProperties();
+ ProvisioningChange delta = setLinkProperties(newLp);
+ // If we've gotten here and we're still not provisioned treat that as
+ // a total loss of provisioning.
+ //
+ // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+ // there was no usable IPv6 obtained before a non-zero provisioning
+ // timeout expired.
+ //
+ // Regardless: GAME OVER.
+ if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
+ delta = ProvisioningChange.LOST_PROVISIONING;
+ }
+
+ dispatchCallback(delta, newLp);
+ if (delta == ProvisioningChange.LOST_PROVISIONING) {
+ transitionTo(mStoppingState);
+ }
+ }
+
+ private void doImmediateProvisioningFailure(int failureType) {
+ logError("onProvisioningFailure(): %s", failureType);
+ recordMetric(failureType);
+ mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ }
+
+ private boolean startIPv4() {
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mConfiguration.mStaticIpConfig != null) {
+ if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+ handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+ } else {
+ return false;
+ }
+ } else {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName);
+ mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+ }
+
+ return true;
+ }
+
+ private boolean startIPv6() {
+ return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
+ mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
+ mInterfaceCtrl.enableIPv6();
+ }
+
+ private boolean applyInitialConfig(InitialConfiguration config) {
+ // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+ for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+ if (!mInterfaceCtrl.addAddress(addr)) return false;
+ }
+
+ return true;
+ }
+
+ private boolean startIpReachabilityMonitor() {
+ try {
+ mIpReachabilityMonitor = new IpReachabilityMonitor(
+ mContext,
+ mInterfaceName,
+ mLog,
+ new IpReachabilityMonitor.Callback() {
+ @Override
+ public void notifyLost(InetAddress ip, String logMsg) {
+ mCallback.onReachabilityLost(logMsg);
+ }
+ },
+ mMultinetworkPolicyTracker);
+ } catch (IllegalArgumentException iae) {
+ // Failed to start IpReachabilityMonitor. Log it and call
+ // onProvisioningFailure() immediately.
+ //
+ // See http://b/31038971.
+ logError("IpReachabilityMonitor failure: %s", iae);
+ mIpReachabilityMonitor = null;
+ }
+
+ return (mIpReachabilityMonitor != null);
+ }
+
+ private void stopAllIP() {
+ // We don't need to worry about routes, just addresses, because:
+ // - disableIpv6() will clear autoconf IPv6 routes as well, and
+ // - we don't get IPv4 routes from netlink
+ // so we neither react to nor need to wait for changes in either.
+
+ mInterfaceCtrl.disableIPv6();
+ mInterfaceCtrl.clearAllAddresses();
+ }
+
+ class StoppedState extends State {
+ @Override
+ public void enter() {
+ stopAllIP();
+
+ resetLinkProperties();
+ if (mStartTimeMillis > 0) {
+ recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+ mStartTimeMillis = 0;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_TERMINATE_AFTER_STOP:
+ stopStateMachineUpdaters();
+ quit();
+ break;
+
+ case CMD_STOP:
+ break;
+
+ case CMD_START:
+ mConfiguration = (ProvisioningConfiguration) msg.obj;
+ transitionTo(mStartedState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER:
+ mMulticastFiltering = (boolean) msg.obj;
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // Everything is already stopped.
+ logError("Unexpected CMD_ON_QUIT (already stopped).");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StoppingState extends State {
+ @Override
+ public void enter() {
+ if (mDhcpClient == null) {
+ // There's no DHCPv4 for which to wait; proceed to stopped.
+ transitionTo(mStoppedState);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ mDhcpClient = null;
+ transitionTo(mStoppedState);
+ break;
+
+ default:
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StartedState extends State {
+ @Override
+ public void enter() {
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ } else {
+ // Clear all IPv4 and IPv6 before proceeding to RunningState.
+ // Clean up any leftover state from an abnormal exit from
+ // tethering or during an IpClient restart.
+ stopAllIP();
+ }
+ }
+
+ @Override
+ public void exit() {
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ }
+ break;
+
+ case EVENT_PROVISIONING_TIMEOUT:
+ handleProvisioningFailure();
+ break;
+
+ default:
+ // It's safe to process messages out of order because the
+ // only message that can both
+ // a) be received at this time and
+ // b) affect provisioning state
+ // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+
+ boolean readyToProceed() {
+ return (!mLinkProperties.hasIPv4Address() &&
+ !mLinkProperties.hasGlobalIPv6Address());
+ }
+ }
+
+ class RunningState extends State {
+ private ConnectivityPacketTracker mPacketTracker;
+ private boolean mDhcpActionInFlight;
+
+ @Override
+ public void enter() {
+ // Get the Configuration for ApfFilter from Context
+ final boolean filter802_3Frames =
+ mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+
+ final int[] ethTypeBlackList = mContext.getResources().getIntArray(
+ R.array.config_apfEthTypeBlackList);
+
+ mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
+ mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+ // TODO: investigate the effects of any multicast filtering racing/interfering with the
+ // rest of this IP configuration startup.
+ if (mApfFilter == null) {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+
+ mPacketTracker = createPacketTracker();
+ if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+ if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+ transitionTo(mStoppingState);
+ return;
+ }
+
+ if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+ transitionTo(mStoppingState);
+ return;
+ }
+
+ final InitialConfiguration config = mConfiguration.mInitialConfig;
+ if ((config != null) && !applyInitialConfig(config)) {
+ // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ transitionTo(mStoppingState);
+ return;
+ }
+
+ if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+ doImmediateProvisioningFailure(
+ IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+ transitionTo(mStoppingState);
+ return;
+ }
+ }
+
+ @Override
+ public void exit() {
+ stopDhcpAction();
+
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.stop();
+ mIpReachabilityMonitor = null;
+ }
+
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+ mDhcpClient.doQuit();
+ }
+
+ if (mPacketTracker != null) {
+ mPacketTracker.stop();
+ mPacketTracker = null;
+ }
+
+ if (mApfFilter != null) {
+ mApfFilter.shutdown();
+ mApfFilter = null;
+ }
+
+ resetLinkProperties();
+ }
+
+ private ConnectivityPacketTracker createPacketTracker() {
+ try {
+ return new ConnectivityPacketTracker(
+ getHandler(), mNetworkInterface, mConnectivityPacketLog);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private void ensureDhcpAction() {
+ if (!mDhcpActionInFlight) {
+ mCallback.onPreDhcpAction();
+ mDhcpActionInFlight = true;
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mRequestedPreDhcpActionMs;
+ mDhcpActionTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ private void stopDhcpAction() {
+ mDhcpActionTimeoutAlarm.cancel();
+ if (mDhcpActionInFlight) {
+ mCallback.onPostDhcpAction();
+ mDhcpActionInFlight = false;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case CMD_START:
+ logError("ALERT: START received in StartedState. Please fix caller.");
+ break;
+
+ case CMD_CONFIRM:
+ // TODO: Possibly introduce a second type of confirmation
+ // that both probes (a) on-link neighbors and (b) does
+ // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
+ // roams.
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.probeAll();
+ }
+ break;
+
+ case EVENT_PRE_DHCP_ACTION_COMPLETE:
+ // It's possible to reach here if, for example, someone
+ // calls completedPreDhcpAction() after provisioning with
+ // a static IP configuration.
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+ transitionTo(mStoppingState);
+ }
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER: {
+ mMulticastFiltering = (boolean) msg.obj;
+ if (mApfFilter != null) {
+ mApfFilter.setMulticastFilter(mMulticastFiltering);
+ } else {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+ break;
+ }
+
+ case EVENT_DHCPACTION_TIMEOUT:
+ stopDhcpAction();
+ break;
+
+ case DhcpClient.CMD_PRE_DHCP_ACTION:
+ if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+ ensureDhcpAction();
+ } else {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+ final LinkAddress ipAddress = (LinkAddress) msg.obj;
+ if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+ mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+ } else {
+ logError("Failed to set IPv4 address.");
+ dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
+ new LinkProperties(mLinkProperties));
+ transitionTo(mStoppingState);
+ }
+ break;
+ }
+
+ // This message is only received when:
+ //
+ // a) initial address acquisition succeeds,
+ // b) renew succeeds or is NAK'd,
+ // c) rebind succeeds or is NAK'd, or
+ // c) the lease expires,
+ //
+ // but never when initial address acquisition fails. The latter
+ // condition is now governed by the provisioning timeout.
+ case DhcpClient.CMD_POST_DHCP_ACTION:
+ stopDhcpAction();
+
+ switch (msg.arg1) {
+ case DhcpClient.DHCP_SUCCESS:
+ handleIPv4Success((DhcpResults) msg.obj);
+ break;
+ case DhcpClient.DHCP_FAILURE:
+ handleIPv4Failure();
+ break;
+ default:
+ logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+ }
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // DHCPv4 quit early for some reason.
+ logError("Unexpected CMD_ON_QUIT.");
+ mDhcpClient = null;
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ private static class MessageHandlingLogger {
+ public String processedInState;
+ public String receivedInState;
+
+ public void reset() {
+ processedInState = null;
+ receivedInState = null;
+ }
+
+ public void handled(State processedIn, IState receivedIn) {
+ processedInState = processedIn.getClass().getSimpleName();
+ receivedInState = receivedIn.getName();
+ }
+
+ public String toString() {
+ return String.format("rcvd_in=%s, proc_in=%s",
+ receivedInState, processedInState);
+ }
+ }
+
+ // TODO: extract out into CollectionUtils.
+ static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+
+ static <T> String join(String delimiter, Collection<T> coll) {
+ return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+ }
+
+ static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+ for (T t: coll) {
+ if (fn.test(t)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+ return coll.stream().filter(fn).collect(Collectors.toList());
+ }
+}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index e33f6c9..b12cb32 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -16,132 +16,112 @@
package android.net.ip;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
-
import android.content.Context;
-import android.net.DhcpResults;
import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
import android.net.LinkProperties;
import android.net.Network;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.SparseArray;
+import android.net.apf.ApfCapabilities;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.net.NetlinkTracker;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-/**
- * IpManager
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpManager is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
+/*
+ * TODO: Delete this altogether in favor of its renamed successor: IpClient.
*
* @hide
*/
-public class IpManager extends StateMachine {
- private static final boolean DBG = false;
+public class IpManager extends IpClient {
+ public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
+ public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
+ super(ipcConfig);
+ }
- // For message logging.
- private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
- private static final SparseArray<String> sWhatToString =
- MessageUtils.findMessageNames(sMessageClasses);
+ public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
+ @Override
+ public Builder withoutIPv4() {
+ super.withoutIPv4();
+ return this;
+ }
+ @Override
+ public Builder withoutIPv6() {
+ super.withoutIPv6();
+ return this;
+ }
+ @Override
+ public Builder withoutIpReachabilityMonitor() {
+ super.withoutIpReachabilityMonitor();
+ return this;
+ }
+ @Override
+ public Builder withPreDhcpAction() {
+ super.withPreDhcpAction();
+ return this;
+ }
+ @Override
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ super.withPreDhcpAction(dhcpActionTimeoutMs);
+ return this;
+ }
+ // No Override; locally defined type.
+ public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+ super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
+ return this;
+ }
+ @Override
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ super.withStaticConfiguration(staticConfig);
+ return this;
+ }
+ @Override
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ super.withApfCapabilities(apfCapabilities);
+ return this;
+ }
+ @Override
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ super.withProvisioningTimeoutMs(timeoutMs);
+ return this;
+ }
+ @Override
+ public Builder withIPv6AddrGenModeEUI64() {
+ super.withIPv6AddrGenModeEUI64();
+ return this;
+ }
+ @Override
+ public Builder withIPv6AddrGenModeStablePrivacy() {
+ super.withIPv6AddrGenModeStablePrivacy();
+ return this;
+ }
+ @Override
+ public Builder withNetwork(Network network) {
+ super.withNetwork(network);
+ return this;
+ }
+ @Override
+ public Builder withDisplayName(String displayName) {
+ super.withDisplayName(displayName);
+ return this;
+ }
+ @Override
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(super.build());
+ }
+ }
+ }
- /**
- * Callbacks for handling IpManager events.
- */
- public static class Callback {
- // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
- // when constructing a ProvisioningConfiguration.
- //
- // Implementations of onPreDhcpAction() must call
- // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
- // to proceed.
- public void onPreDhcpAction() {}
- public void onPostDhcpAction() {}
+ public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+ return new ProvisioningConfiguration.Builder();
+ }
- // This is purely advisory and not an indication of provisioning
- // success or failure. This is only here for callers that want to
- // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
- // DHCPv4 or static IPv4 configuration failure or success can be
- // determined by whether or not the passed-in DhcpResults object is
- // null or not.
- public void onNewDhcpResults(DhcpResults dhcpResults) {}
+ public static class InitialConfiguration extends IpClient.InitialConfiguration {
+ }
- public void onProvisioningSuccess(LinkProperties newLp) {}
- public void onProvisioningFailure(LinkProperties newLp) {}
-
- // Invoked on LinkProperties changes.
- public void onLinkPropertiesChange(LinkProperties newLp) {}
-
- // Called when the internal IpReachabilityMonitor (if enabled) has
- // detected the loss of a critical number of required neighbors.
- public void onReachabilityLost(String logMsg) {}
-
- // Called when the IpManager state machine terminates.
- public void onQuit() {}
-
- // Install an APF program to filter incoming packets.
- public void installPacketFilter(byte[] filter) {}
-
- // If multicast filtering cannot be accomplished with APF, this function will be called to
- // actuate multicast filtering using another means.
- public void setFallbackMulticastFilter(boolean enabled) {}
-
- // Enabled/disable Neighbor Discover offload functionality. This is
- // called, for example, whenever 464xlat is being started or stopped.
- public void setNeighborDiscoveryOffload(boolean enable) {}
+ public static class Callback extends IpClient.Callback {
}
public static class WaitForProvisioningCallback extends Callback {
@@ -173,1569 +153,24 @@
}
}
- // Use a wrapper class to log in order to ensure complete and detailed
- // logging. This method is lighter weight than annotations/reflection
- // and has the following benefits:
- //
- // - No invoked method can be forgotten.
- // Any new method added to IpManager.Callback must be overridden
- // here or it will never be called.
- //
- // - No invoking call site can be forgotten.
- // Centralized logging in this way means call sites don't need to
- // remember to log, and therefore no call site can be forgotten.
- //
- // - No variation in log format among call sites.
- // Encourages logging of any available arguments, and all call sites
- // are necessarily logged identically.
- //
- // TODO: Find an lighter weight approach.
- private class LoggingCallbackWrapper extends Callback {
- private static final String PREFIX = "INVOKE ";
- private Callback mCallback;
-
- public LoggingCallbackWrapper(Callback callback) {
- mCallback = callback;
- }
-
- private void log(String msg) {
- mLog.log(PREFIX + msg);
- }
-
- @Override
- public void onPreDhcpAction() {
- mCallback.onPreDhcpAction();
- log("onPreDhcpAction()");
- }
- @Override
- public void onPostDhcpAction() {
- mCallback.onPostDhcpAction();
- log("onPostDhcpAction()");
- }
- @Override
- public void onNewDhcpResults(DhcpResults dhcpResults) {
- mCallback.onNewDhcpResults(dhcpResults);
- log("onNewDhcpResults({" + dhcpResults + "})");
- }
- @Override
- public void onProvisioningSuccess(LinkProperties newLp) {
- mCallback.onProvisioningSuccess(newLp);
- log("onProvisioningSuccess({" + newLp + "})");
- }
- @Override
- public void onProvisioningFailure(LinkProperties newLp) {
- mCallback.onProvisioningFailure(newLp);
- log("onProvisioningFailure({" + newLp + "})");
- }
- @Override
- public void onLinkPropertiesChange(LinkProperties newLp) {
- mCallback.onLinkPropertiesChange(newLp);
- log("onLinkPropertiesChange({" + newLp + "})");
- }
- @Override
- public void onReachabilityLost(String logMsg) {
- mCallback.onReachabilityLost(logMsg);
- log("onReachabilityLost(" + logMsg + ")");
- }
- @Override
- public void onQuit() {
- mCallback.onQuit();
- log("onQuit()");
- }
- @Override
- public void installPacketFilter(byte[] filter) {
- mCallback.installPacketFilter(filter);
- log("installPacketFilter(byte[" + filter.length + "])");
- }
- @Override
- public void setFallbackMulticastFilter(boolean enabled) {
- mCallback.setFallbackMulticastFilter(enabled);
- log("setFallbackMulticastFilter(" + enabled + ")");
- }
- @Override
- public void setNeighborDiscoveryOffload(boolean enable) {
- mCallback.setNeighborDiscoveryOffload(enable);
- log("setNeighborDiscoveryOffload(" + enable + ")");
- }
- }
-
- /**
- * This class encapsulates parameters to be passed to
- * IpManager#startProvisioning(). A defensive copy is made by IpManager
- * and the values specified herein are in force until IpManager#stop()
- * is called.
- *
- * Example use:
- *
- * final ProvisioningConfiguration config =
- * mIpManager.buildProvisioningConfiguration()
- * .withPreDhcpAction()
- * .withProvisioningTimeoutMs(36 * 1000)
- * .build();
- * mIpManager.startProvisioning(config);
- * ...
- * mIpManager.stop();
- *
- * The specified provisioning configuration will only be active until
- * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
- * must specify the configuration again.
- */
- public static class ProvisioningConfiguration {
- // TODO: Delete this default timeout once those callers that care are
- // fixed to pass in their preferred timeout.
- //
- // We pick 36 seconds so we can send DHCP requests at
- //
- // t=0, t=2, t=6, t=14, t=30
- //
- // allowing for 10% jitter.
- private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
-
- public static class Builder {
- private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
- public Builder withoutIPv4() {
- mConfig.mEnableIPv4 = false;
- return this;
- }
-
- public Builder withoutIPv6() {
- mConfig.mEnableIPv6 = false;
- return this;
- }
-
- public Builder withoutIpReachabilityMonitor() {
- mConfig.mUsingIpReachabilityMonitor = false;
- return this;
- }
-
- public Builder withPreDhcpAction() {
- mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
- return this;
- }
-
- public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
- mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
- return this;
- }
-
- public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
- mConfig.mInitialConfig = initialConfig;
- return this;
- }
-
- public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
- mConfig.mStaticIpConfig = staticConfig;
- return this;
- }
-
- public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
- mConfig.mApfCapabilities = apfCapabilities;
- return this;
- }
-
- public Builder withProvisioningTimeoutMs(int timeoutMs) {
- mConfig.mProvisioningTimeoutMs = timeoutMs;
- return this;
- }
-
- public Builder withIPv6AddrGenModeEUI64() {
- mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
- return this;
- }
-
- public Builder withIPv6AddrGenModeStablePrivacy() {
- mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
- return this;
- }
-
- public Builder withNetwork(Network network) {
- mConfig.mNetwork = network;
- return this;
- }
-
- public Builder withDisplayName(String displayName) {
- mConfig.mDisplayName = displayName;
- return this;
- }
-
- public ProvisioningConfiguration build() {
- return new ProvisioningConfiguration(mConfig);
- }
- }
-
- /* package */ boolean mEnableIPv4 = true;
- /* package */ boolean mEnableIPv6 = true;
- /* package */ boolean mUsingIpReachabilityMonitor = true;
- /* package */ int mRequestedPreDhcpActionMs;
- /* package */ InitialConfiguration mInitialConfig;
- /* package */ StaticIpConfiguration mStaticIpConfig;
- /* package */ ApfCapabilities mApfCapabilities;
- /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
- /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
- /* package */ Network mNetwork = null;
- /* package */ String mDisplayName = null;
-
- public ProvisioningConfiguration() {} // used by Builder
-
- public ProvisioningConfiguration(ProvisioningConfiguration other) {
- mEnableIPv4 = other.mEnableIPv4;
- mEnableIPv6 = other.mEnableIPv6;
- mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
- mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
- mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
- mStaticIpConfig = other.mStaticIpConfig;
- mApfCapabilities = other.mApfCapabilities;
- mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
- mIPv6AddrGenMode = other.mIPv6AddrGenMode;
- mNetwork = other.mNetwork;
- mDisplayName = other.mDisplayName;
- }
-
- @Override
- public String toString() {
- return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
- .add("mEnableIPv4: " + mEnableIPv4)
- .add("mEnableIPv6: " + mEnableIPv6)
- .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
- .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
- .add("mInitialConfig: " + mInitialConfig)
- .add("mStaticIpConfig: " + mStaticIpConfig)
- .add("mApfCapabilities: " + mApfCapabilities)
- .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
- .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
- .add("mNetwork: " + mNetwork)
- .add("mDisplayName: " + mDisplayName)
- .toString();
- }
-
- public boolean isValid() {
- return (mInitialConfig == null) || mInitialConfig.isValid();
- }
- }
-
- public static class InitialConfiguration {
- public final Set<LinkAddress> ipAddresses = new HashSet<>();
- public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
- public final Set<InetAddress> dnsServers = new HashSet<>();
- public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
-
- public static InitialConfiguration copy(InitialConfiguration config) {
- if (config == null) {
- return null;
- }
- InitialConfiguration configCopy = new InitialConfiguration();
- configCopy.ipAddresses.addAll(config.ipAddresses);
- configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
- configCopy.dnsServers.addAll(config.dnsServers);
- return configCopy;
- }
-
- @Override
- public String toString() {
- return String.format(
- "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
- join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
- join(", ", dnsServers), gateway);
- }
-
- public boolean isValid() {
- if (ipAddresses.isEmpty()) {
- return false;
- }
-
- // For every IP address, there must be at least one prefix containing that address.
- for (LinkAddress addr : ipAddresses) {
- if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
- return false;
- }
- }
- // For every dns server, there must be at least one prefix containing that address.
- for (InetAddress addr : dnsServers) {
- if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
- return false;
- }
- }
- // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
- // (read: compliant with RFC4291#section2.5.4).
- if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
- return false;
- }
- // If directlyConnectedRoutes contains an IPv6 default route
- // then ipAddresses MUST contain at least one non-ULA GUA.
- if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
- && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
- return false;
- }
- // The prefix length of routes in directlyConnectedRoutes be within reasonable
- // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
- if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
- return false;
- }
- // There no more than one IPv4 address
- if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
- return false;
- }
-
- return true;
- }
-
- /**
- * @return true if the given list of addressess and routes satisfies provisioning for this
- * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
- * because addresses and routes seen by Netlink will contain additional fields like flags,
- * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
- * provisioning check always fails.
- *
- * If the given list of routes is null, only addresses are taken into considerations.
- */
- public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
- if (ipAddresses.isEmpty()) {
- return false;
- }
-
- for (LinkAddress addr : ipAddresses) {
- if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
- return false;
- }
- }
-
- if (routes != null) {
- for (IpPrefix prefix : directlyConnectedRoutes) {
- if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
- return !route.hasGateway() && prefix.equals(route.getDestination());
- }
-
- private static boolean isPrefixLengthCompliant(LinkAddress addr) {
- return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
- }
-
- private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
- return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
- }
-
- private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
- return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
- && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
- }
-
- private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
- return prefix.getAddress().equals(Inet6Address.ANY);
- }
-
- private static boolean isIPv6GUA(LinkAddress addr) {
- return addr.isIPv6() && addr.isGlobalPreferred();
- }
- }
-
- public static final String DUMP_ARG = "ipmanager";
- public static final String DUMP_ARG_CONFIRM = "confirm";
-
- private static final int CMD_TERMINATE_AFTER_STOP = 1;
- private static final int CMD_STOP = 2;
- private static final int CMD_START = 3;
- private static final int CMD_CONFIRM = 4;
- private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
- // Sent by NetlinkTracker to communicate netlink events.
- private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
- private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
- private static final int CMD_UPDATE_HTTP_PROXY = 8;
- private static final int CMD_SET_MULTICAST_FILTER = 9;
- private static final int EVENT_PROVISIONING_TIMEOUT = 10;
- private static final int EVENT_DHCPACTION_TIMEOUT = 11;
-
- private static final int MAX_LOG_RECORDS = 500;
- private static final int MAX_PACKET_RECORDS = 100;
-
- private static final boolean NO_CALLBACKS = false;
- private static final boolean SEND_CALLBACKS = true;
-
- // This must match the interface prefix in clatd.c.
- // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
- private static final String CLAT_PREFIX = "v4-";
-
- private final State mStoppedState = new StoppedState();
- private final State mStoppingState = new StoppingState();
- private final State mStartedState = new StartedState();
- private final State mRunningState = new RunningState();
-
- private final String mTag;
- private final Context mContext;
- private final String mInterfaceName;
- private final String mClatInterfaceName;
- @VisibleForTesting
- protected final Callback mCallback;
- private final INetworkManagementService mNwService;
- private final NetlinkTracker mNetlinkTracker;
- private final WakeupMessage mProvisioningTimeoutAlarm;
- private final WakeupMessage mDhcpActionTimeoutAlarm;
- private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
- private final SharedLog mLog;
- private final LocalLog mConnectivityPacketLog;
- private final MessageHandlingLogger mMsgStateLogger;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
- private final InterfaceController mInterfaceCtrl;
-
- private NetworkInterface mNetworkInterface;
-
- /**
- * Non-final member variables accessed only from within our StateMachine.
- */
- private LinkProperties mLinkProperties;
- private ProvisioningConfiguration mConfiguration;
- private IpReachabilityMonitor mIpReachabilityMonitor;
- private DhcpClient mDhcpClient;
- private DhcpResults mDhcpResults;
- private String mTcpBufferSizes;
- private ProxyInfo mHttpProxy;
- private ApfFilter mApfFilter;
- private boolean mMulticastFiltering;
- private long mStartTimeMillis;
-
public IpManager(Context context, String ifName, Callback callback) {
this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
NetdService.getInstance());
}
- /**
- * An expanded constructor, useful for dependency injection.
- * TODO: migrate all test users to mock IpManager directly and remove this ctor.
- */
public IpManager(Context context, String ifName, Callback callback,
INetworkManagementService nwService) {
this(context, ifName, callback, nwService, NetdService.getInstance());
}
@VisibleForTesting
- IpManager(Context context, String ifName, Callback callback,
+ public IpManager(Context context, String ifName, Callback callback,
INetworkManagementService nwService, INetd netd) {
- super(IpManager.class.getSimpleName() + "." + ifName);
- mTag = getName();
-
- mContext = context;
- mInterfaceName = ifName;
- mClatInterfaceName = CLAT_PREFIX + ifName;
- mCallback = new LoggingCallbackWrapper(callback);
- mNwService = nwService;
-
- mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
- mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
- mMsgStateLogger = new MessageHandlingLogger();
-
- mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
-
- mNetlinkTracker = new NetlinkTracker(
- mInterfaceName,
- new NetlinkTracker.Callback() {
- @Override
- public void update() {
- sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- }
- }) {
- @Override
- public void interfaceAdded(String iface) {
- super.interfaceAdded(iface);
- if (mClatInterfaceName.equals(iface)) {
- mCallback.setNeighborDiscoveryOffload(false);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceAdded(" + iface +")";
- logMsg(msg);
- }
-
- @Override
- public void interfaceRemoved(String iface) {
- super.interfaceRemoved(iface);
- // TODO: Also observe mInterfaceName going down and take some
- // kind of appropriate action.
- if (mClatInterfaceName.equals(iface)) {
- // TODO: consider sending a message to the IpManager main
- // StateMachine thread, in case "NDO enabled" state becomes
- // tied to more things that 464xlat operation.
- mCallback.setNeighborDiscoveryOffload(true);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceRemoved(" + iface +")";
- logMsg(msg);
- }
-
- private void logMsg(String msg) {
- Log.d(mTag, msg);
- getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
- }
- };
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
-
- mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
- () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
-
- mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
- mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
- // Anything the StateMachine may access must have been instantiated
- // before this point.
- configureAndStartStateMachine();
-
- // Anything that may send messages to the StateMachine must only be
- // configured to do so after the StateMachine has started (above).
- startStateMachineUpdaters();
- }
-
- private void configureAndStartStateMachine() {
- addState(mStoppedState);
- addState(mStartedState);
- addState(mRunningState, mStartedState);
- addState(mStoppingState);
-
- setInitialState(mStoppedState);
-
- super.start();
- }
-
- private void startStateMachineUpdaters() {
- try {
- mNwService.registerObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't register NetlinkTracker: %s", e);
- }
-
- mMultinetworkPolicyTracker.start();
- }
-
- private void stopStateMachineUpdaters() {
- try {
- mNwService.unregisterObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't unregister NetlinkTracker: %s", e);
- }
-
- mMultinetworkPolicyTracker.shutdown();
- }
-
- @Override
- protected void onQuitting() {
- mCallback.onQuit();
- }
-
- // Shut down this IpManager instance altogether.
- public void shutdown() {
- stop();
- sendMessage(CMD_TERMINATE_AFTER_STOP);
- }
-
- public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
- return new ProvisioningConfiguration.Builder();
+ super(context, ifName, callback, nwService, netd);
}
public void startProvisioning(ProvisioningConfiguration req) {
- if (!req.isValid()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- return;
- }
-
- getNetworkInterface();
-
- mCallback.setNeighborDiscoveryOffload(true);
- sendMessage(CMD_START, new ProvisioningConfiguration(req));
- }
-
- // TODO: Delete this.
- public void startProvisioning(StaticIpConfiguration staticIpConfig) {
- startProvisioning(buildProvisioningConfiguration()
- .withStaticConfiguration(staticIpConfig)
- .build());
- }
-
- public void startProvisioning() {
- startProvisioning(new ProvisioningConfiguration());
- }
-
- public void stop() {
- sendMessage(CMD_STOP);
- }
-
- public void confirmConfiguration() {
- sendMessage(CMD_CONFIRM);
- }
-
- public void completedPreDhcpAction() {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
-
- /**
- * Set the TCP buffer sizes to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setTcpBufferSizes(String tcpBufferSizes) {
- sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
- }
-
- /**
- * Set the HTTP Proxy configuration to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setHttpProxy(ProxyInfo proxyInfo) {
- sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
- }
-
- /**
- * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
- * if not, Callback.setFallbackMulticastFilter() is called.
- */
- public void setMulticastFilter(boolean enabled) {
- sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
- }
-
- public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
- // Execute confirmConfiguration() and take no further action.
- confirmConfiguration();
- return;
- }
-
- // Thread-unsafe access to mApfFilter but just used for debugging.
- final ApfFilter apfFilter = mApfFilter;
- final ProvisioningConfiguration provisioningConfig = mConfiguration;
- final ApfCapabilities apfCapabilities = (provisioningConfig != null)
- ? provisioningConfig.mApfCapabilities : null;
-
- IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println(mTag + " APF dump:");
- pw.increaseIndent();
- if (apfFilter != null) {
- apfFilter.dump(pw);
- } else {
- pw.print("No active ApfFilter; ");
- if (provisioningConfig == null) {
- pw.println("IpManager not yet started.");
- } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
- pw.println("Hardware does not support APF.");
- } else {
- pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
- }
- }
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " current ProvisioningConfiguration:");
- pw.increaseIndent();
- pw.println(Objects.toString(provisioningConfig, "N/A"));
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " StateMachine dump:");
- pw.increaseIndent();
- mLog.dump(fd, pw, args);
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " connectivity packet log:");
- pw.println();
- pw.println("Debug with python and scapy via:");
- pw.println("shell$ python");
- pw.println(">>> from scapy import all as scapy");
- pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
- pw.println();
-
- pw.increaseIndent();
- mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
- pw.decreaseIndent();
- }
-
-
- /**
- * Internals.
- */
-
- @Override
- protected String getWhatToString(int what) {
- return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
- }
-
- @Override
- protected String getLogRecString(Message msg) {
- final String logLine = String.format(
- "%s/%d %d %d %s [%s]",
- mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
- msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
- final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
- mLog.log(richerLogLine);
- if (DBG) {
- Log.d(mTag, richerLogLine);
- }
-
- mMsgStateLogger.reset();
- return logLine;
- }
-
- @Override
- protected boolean recordLogRec(Message msg) {
- // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
- // and we already log any LinkProperties change that results in an
- // invocation of IpManager.Callback#onLinkPropertiesChange().
- final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- if (!shouldLog) {
- mMsgStateLogger.reset();
- }
- return shouldLog;
- }
-
- private void logError(String fmt, Object... args) {
- final String msg = "ERROR " + String.format(fmt, args);
- Log.e(mTag, msg);
- mLog.log(msg);
- }
-
- private void getNetworkInterface() {
- try {
- mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
- } catch (SocketException | NullPointerException e) {
- // TODO: throw new IllegalStateException.
- logError("Failed to get interface object: %s", e);
- }
- }
-
- // This needs to be called with care to ensure that our LinkProperties
- // are in sync with the actual LinkProperties of the interface. For example,
- // we should only call this if we know for sure that there are no IP addresses
- // assigned to the interface, etc.
- private void resetLinkProperties() {
- mNetlinkTracker.clearLinkProperties();
- mConfiguration = null;
- mDhcpResults = null;
- mTcpBufferSizes = "";
- mHttpProxy = null;
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
- }
-
- private void recordMetric(final int type) {
- if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
- final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
- mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
- }
-
- // For now: use WifiStateMachine's historical notion of provisioned.
- @VisibleForTesting
- static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
- // For historical reasons, we should connect even if all we have is
- // an IPv4 address and nothing else.
- if (lp.hasIPv4Address() || lp.isProvisioned()) {
- return true;
- }
- if (config == null) {
- return false;
- }
-
- // When an InitialConfiguration is specified, ignore any difference with previous
- // properties and instead check if properties observed match the desired properties.
- return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
- }
-
- // TODO: Investigate folding all this into the existing static function
- // LinkProperties.compareProvisioning() or some other single function that
- // takes two LinkProperties objects and returns a ProvisioningChange
- // object that is a correct and complete assessment of what changed, taking
- // account of the asymmetries described in the comments in this function.
- // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
- private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
- ProvisioningChange delta;
- InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
- final boolean wasProvisioned = isProvisioned(oldLp, config);
- final boolean isProvisioned = isProvisioned(newLp, config);
-
- if (!wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.GAINED_PROVISIONING;
- } else if (wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.STILL_PROVISIONED;
- } else if (!wasProvisioned && !isProvisioned) {
- delta = ProvisioningChange.STILL_NOT_PROVISIONED;
- } else {
- // (wasProvisioned && !isProvisioned)
- //
- // Note that this is true even if we lose a configuration element
- // (e.g., a default gateway) that would not be required to advance
- // into provisioned state. This is intended: if we have a default
- // router and we lose it, that's a sure sign of a problem, but if
- // we connect to a network with no IPv4 DNS servers, we consider
- // that to be a network without DNS servers and connect anyway.
- //
- // See the comment below.
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
- final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
- final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
- // If bad wifi avoidance is disabled, then ignore IPv6 loss of
- // provisioning. Otherwise, when a hotspot that loses Internet
- // access sends out a 0-lifetime RA to its clients, the clients
- // will disconnect and then reconnect, avoiding the bad hotspot,
- // instead of getting stuck on the bad hotspot. http://b/31827713 .
- //
- // This is incorrect because if the hotspot then regains Internet
- // access with a different prefix, TCP connections on the
- // deprecated addresses will remain stuck.
- //
- // Note that we can still be disconnected by IpReachabilityMonitor
- // if the IPv6 default gateway (but not the IPv6 DNS servers; see
- // accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
- // Additionally:
- //
- // Partial configurations (e.g., only an IPv4 address with no DNS
- // servers and no default route) are accepted as long as DHCPv4
- // succeeds. On such a network, isProvisioned() will always return
- // false, because the configuration is not complete, but we want to
- // connect anyway. It might be a disconnected network such as a
- // Chromecast or a wireless printer, for example.
- //
- // Because on such a network isProvisioned() will always return false,
- // delta will never be LOST_PROVISIONING. So check for loss of
- // provisioning here too.
- if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- // Additionally:
- //
- // If the previous link properties had a global IPv6 address and an
- // IPv6 default route then also consider the loss of that default route
- // to be a loss of provisioning. See b/27962810.
- if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- return delta;
- }
-
- private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
- switch (delta) {
- case GAINED_PROVISIONING:
- if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
- recordMetric(IpManagerEvent.PROVISIONING_OK);
- mCallback.onProvisioningSuccess(newLp);
- break;
-
- case LOST_PROVISIONING:
- if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
- recordMetric(IpManagerEvent.PROVISIONING_FAIL);
- mCallback.onProvisioningFailure(newLp);
- break;
-
- default:
- if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
- mCallback.onLinkPropertiesChange(newLp);
- break;
- }
- }
-
- // Updates all IpManager-related state concerned with LinkProperties.
- // Returns a ProvisioningChange for possibly notifying other interested
- // parties that are not fronted by IpManager.
- private ProvisioningChange setLinkProperties(LinkProperties newLp) {
- if (mApfFilter != null) {
- mApfFilter.setLinkProperties(newLp);
- }
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.updateLinkProperties(newLp);
- }
-
- ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
- mLinkProperties = new LinkProperties(newLp);
-
- if (delta == ProvisioningChange.GAINED_PROVISIONING) {
- // TODO: Add a proper ProvisionedState and cancel the alarm in
- // its enter() method.
- mProvisioningTimeoutAlarm.cancel();
- }
-
- return delta;
- }
-
- private LinkProperties assembleLinkProperties() {
- // [1] Create a new LinkProperties object to populate.
- LinkProperties newLp = new LinkProperties();
- newLp.setInterfaceName(mInterfaceName);
-
- // [2] Pull in data from netlink:
- // - IPv4 addresses
- // - IPv6 addresses
- // - IPv6 routes
- // - IPv6 DNS servers
- //
- // N.B.: this is fundamentally race-prone and should be fixed by
- // changing NetlinkTracker from a hybrid edge/level model to an
- // edge-only model, or by giving IpManager its own netlink socket(s)
- // so as to track all required information directly.
- LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
- newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
- for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
- // [3] Add in data from DHCPv4, if available.
- //
- // mDhcpResults is never shared with any other owner so we don't have
- // to worry about concurrent modification.
- if (mDhcpResults != null) {
- for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
- newLp.setDomains(mDhcpResults.domains);
-
- if (mDhcpResults.mtu != 0) {
- newLp.setMtu(mDhcpResults.mtu);
- }
- }
-
- // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
- if (!TextUtils.isEmpty(mTcpBufferSizes)) {
- newLp.setTcpBufferSizes(mTcpBufferSizes);
- }
- if (mHttpProxy != null) {
- newLp.setHttpProxy(mHttpProxy);
- }
-
- // [5] Add data from InitialConfiguration
- if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
- InitialConfiguration config = mConfiguration.mInitialConfig;
- // Add InitialConfiguration routes and dns server addresses once all addresses
- // specified in the InitialConfiguration have been observed with Netlink.
- if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
- for (IpPrefix prefix : config.directlyConnectedRoutes) {
- newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
- }
- }
- addAllReachableDnsServers(newLp, config.dnsServers);
- }
- final LinkProperties oldLp = mLinkProperties;
- if (DBG) {
- Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
- netlinkLinkProperties, newLp, oldLp));
- }
-
- // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
- // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
- return newLp;
- }
-
- private static void addAllReachableDnsServers(
- LinkProperties lp, Iterable<InetAddress> dnses) {
- // TODO: Investigate deleting this reachability check. We should be
- // able to pass everything down to netd and let netd do evaluation
- // and RFC6724-style sorting.
- for (InetAddress dns : dnses) {
- if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
- lp.addDnsServer(dns);
- }
- }
- }
-
- // Returns false if we have lost provisioning, true otherwise.
- private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
- final LinkProperties newLp = assembleLinkProperties();
- if (Objects.equals(newLp, mLinkProperties)) {
- return true;
- }
- final ProvisioningChange delta = setLinkProperties(newLp);
- if (sendCallbacks) {
- dispatchCallback(delta, newLp);
- }
- return (delta != ProvisioningChange.LOST_PROVISIONING);
- }
-
- private void handleIPv4Success(DhcpResults dhcpResults) {
- mDhcpResults = new DhcpResults(dhcpResults);
- final LinkProperties newLp = assembleLinkProperties();
- final ProvisioningChange delta = setLinkProperties(newLp);
-
- if (DBG) {
- Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
- }
- mCallback.onNewDhcpResults(dhcpResults);
- dispatchCallback(delta, newLp);
- }
-
- private void handleIPv4Failure() {
- // TODO: Investigate deleting this clearIPv4Address() call.
- //
- // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
- // that could trigger a call to this function. If we missed handling
- // that message in StartedState for some reason we would still clear
- // any addresses upon entry to StoppedState.
- mInterfaceCtrl.clearIPv4Address();
- mDhcpResults = null;
- if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
- mCallback.onNewDhcpResults(null);
-
- handleProvisioningFailure();
- }
-
- private void handleProvisioningFailure() {
- final LinkProperties newLp = assembleLinkProperties();
- ProvisioningChange delta = setLinkProperties(newLp);
- // If we've gotten here and we're still not provisioned treat that as
- // a total loss of provisioning.
- //
- // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
- // there was no usable IPv6 obtained before a non-zero provisioning
- // timeout expired.
- //
- // Regardless: GAME OVER.
- if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- dispatchCallback(delta, newLp);
- if (delta == ProvisioningChange.LOST_PROVISIONING) {
- transitionTo(mStoppingState);
- }
- }
-
- private void doImmediateProvisioningFailure(int failureType) {
- logError("onProvisioningFailure(): %s", failureType);
- recordMetric(failureType);
- mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
- }
-
- private boolean startIPv4() {
- // If we have a StaticIpConfiguration attempt to apply it and
- // handle the result accordingly.
- if (mConfiguration.mStaticIpConfig != null) {
- if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
- handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
- } else {
- return false;
- }
- } else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
- }
-
- return true;
- }
-
- private boolean startIPv6() {
- return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
- mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
- mInterfaceCtrl.enableIPv6();
- }
-
- private boolean applyInitialConfig(InitialConfiguration config) {
- // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
- for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
- if (!mInterfaceCtrl.addAddress(addr)) return false;
- }
-
- return true;
- }
-
- private boolean startIpReachabilityMonitor() {
- try {
- mIpReachabilityMonitor = new IpReachabilityMonitor(
- mContext,
- mInterfaceName,
- mLog,
- new IpReachabilityMonitor.Callback() {
- @Override
- public void notifyLost(InetAddress ip, String logMsg) {
- mCallback.onReachabilityLost(logMsg);
- }
- },
- mMultinetworkPolicyTracker);
- } catch (IllegalArgumentException iae) {
- // Failed to start IpReachabilityMonitor. Log it and call
- // onProvisioningFailure() immediately.
- //
- // See http://b/31038971.
- logError("IpReachabilityMonitor failure: %s", iae);
- mIpReachabilityMonitor = null;
- }
-
- return (mIpReachabilityMonitor != null);
- }
-
- private void stopAllIP() {
- // We don't need to worry about routes, just addresses, because:
- // - disableIpv6() will clear autoconf IPv6 routes as well, and
- // - we don't get IPv4 routes from netlink
- // so we neither react to nor need to wait for changes in either.
-
- mInterfaceCtrl.disableIPv6();
- mInterfaceCtrl.clearAllAddresses();
- }
-
- class StoppedState extends State {
- @Override
- public void enter() {
- stopAllIP();
-
- resetLinkProperties();
- if (mStartTimeMillis > 0) {
- recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
- mStartTimeMillis = 0;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_TERMINATE_AFTER_STOP:
- stopStateMachineUpdaters();
- quit();
- break;
-
- case CMD_STOP:
- break;
-
- case CMD_START:
- mConfiguration = (ProvisioningConfiguration) msg.obj;
- transitionTo(mStartedState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER:
- mMulticastFiltering = (boolean) msg.obj;
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // Everything is already stopped.
- logError("Unexpected CMD_ON_QUIT (already stopped).");
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StoppingState extends State {
- @Override
- public void enter() {
- if (mDhcpClient == null) {
- // There's no DHCPv4 for which to wait; proceed to stopped.
- transitionTo(mStoppedState);
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_STOP:
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- mDhcpClient = null;
- transitionTo(mStoppedState);
- break;
-
- default:
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StartedState extends State {
- @Override
- public void enter() {
- mStartTimeMillis = SystemClock.elapsedRealtime();
-
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
-
- if (readyToProceed()) {
- transitionTo(mRunningState);
- } else {
- // Clear all IPv4 and IPv6 before proceeding to RunningState.
- // Clean up any leftover state from an abnormal exit from
- // tethering or during an IpManager restart.
- stopAllIP();
- }
- }
-
- @Override
- public void exit() {
- mProvisioningTimeoutAlarm.cancel();
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- if (readyToProceed()) {
- transitionTo(mRunningState);
- }
- break;
-
- case EVENT_PROVISIONING_TIMEOUT:
- handleProvisioningFailure();
- break;
-
- default:
- // It's safe to process messages out of order because the
- // only message that can both
- // a) be received at this time and
- // b) affect provisioning state
- // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
-
- boolean readyToProceed() {
- return (!mLinkProperties.hasIPv4Address() &&
- !mLinkProperties.hasGlobalIPv6Address());
- }
- }
-
- class RunningState extends State {
- private ConnectivityPacketTracker mPacketTracker;
- private boolean mDhcpActionInFlight;
-
- @Override
- public void enter() {
- // Get the Configuration for ApfFilter from Context
- final boolean filter802_3Frames =
- mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
- final int[] ethTypeBlackList = mContext.getResources().getIntArray(
- R.array.config_apfEthTypeBlackList);
-
- mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
- mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
- // TODO: investigate the effects of any multicast filtering racing/interfering with the
- // rest of this IP configuration startup.
- if (mApfFilter == null) {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
-
- mPacketTracker = createPacketTracker();
- if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
-
- if (mConfiguration.mEnableIPv6 && !startIPv6()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
- transitionTo(mStoppingState);
- return;
- }
-
- if (mConfiguration.mEnableIPv4 && !startIPv4()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
- transitionTo(mStoppingState);
- return;
- }
-
- final InitialConfiguration config = mConfiguration.mInitialConfig;
- if ((config != null) && !applyInitialConfig(config)) {
- // TODO introduce a new IpManagerEvent constant to distinguish this error case.
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- transitionTo(mStoppingState);
- return;
- }
-
- if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
- doImmediateProvisioningFailure(
- IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
- transitionTo(mStoppingState);
- return;
- }
- }
-
- @Override
- public void exit() {
- stopDhcpAction();
-
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.stop();
- mIpReachabilityMonitor = null;
- }
-
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
- mDhcpClient.doQuit();
- }
-
- if (mPacketTracker != null) {
- mPacketTracker.stop();
- mPacketTracker = null;
- }
-
- if (mApfFilter != null) {
- mApfFilter.shutdown();
- mApfFilter = null;
- }
-
- resetLinkProperties();
- }
-
- private ConnectivityPacketTracker createPacketTracker() {
- try {
- return new ConnectivityPacketTracker(
- getHandler(), mNetworkInterface, mConnectivityPacketLog);
- } catch (IllegalArgumentException e) {
- return null;
- }
- }
-
- private void ensureDhcpAction() {
- if (!mDhcpActionInFlight) {
- mCallback.onPreDhcpAction();
- mDhcpActionInFlight = true;
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mRequestedPreDhcpActionMs;
- mDhcpActionTimeoutAlarm.schedule(alarmTime);
- }
- }
-
- private void stopDhcpAction() {
- mDhcpActionTimeoutAlarm.cancel();
- if (mDhcpActionInFlight) {
- mCallback.onPostDhcpAction();
- mDhcpActionInFlight = false;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case CMD_START:
- logError("ALERT: START received in StartedState. Please fix caller.");
- break;
-
- case CMD_CONFIRM:
- // TODO: Possibly introduce a second type of confirmation
- // that both probes (a) on-link neighbors and (b) does
- // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
- // roams.
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.probeAll();
- }
- break;
-
- case EVENT_PRE_DHCP_ACTION_COMPLETE:
- // It's possible to reach here if, for example, someone
- // calls completedPreDhcpAction() after provisioning with
- // a static IP configuration.
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppingState);
- }
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER: {
- mMulticastFiltering = (boolean) msg.obj;
- if (mApfFilter != null) {
- mApfFilter.setMulticastFilter(mMulticastFiltering);
- } else {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
- break;
- }
-
- case EVENT_DHCPACTION_TIMEOUT:
- stopDhcpAction();
- break;
-
- case DhcpClient.CMD_PRE_DHCP_ACTION:
- if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
- ensureDhcpAction();
- } else {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
- final LinkAddress ipAddress = (LinkAddress) msg.obj;
- if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
- mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
- } else {
- logError("Failed to set IPv4 address.");
- dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
- new LinkProperties(mLinkProperties));
- transitionTo(mStoppingState);
- }
- break;
- }
-
- // This message is only received when:
- //
- // a) initial address acquisition succeeds,
- // b) renew succeeds or is NAK'd,
- // c) rebind succeeds or is NAK'd, or
- // c) the lease expires,
- //
- // but never when initial address acquisition fails. The latter
- // condition is now governed by the provisioning timeout.
- case DhcpClient.CMD_POST_DHCP_ACTION:
- stopDhcpAction();
-
- switch (msg.arg1) {
- case DhcpClient.DHCP_SUCCESS:
- handleIPv4Success((DhcpResults) msg.obj);
- break;
- case DhcpClient.DHCP_FAILURE:
- handleIPv4Failure();
- break;
- default:
- logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
- }
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // DHCPv4 quit early for some reason.
- logError("Unexpected CMD_ON_QUIT.");
- mDhcpClient = null;
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- private static class MessageHandlingLogger {
- public String processedInState;
- public String receivedInState;
-
- public void reset() {
- processedInState = null;
- receivedInState = null;
- }
-
- public void handled(State processedIn, IState receivedIn) {
- processedInState = processedIn.getClass().getSimpleName();
- receivedInState = receivedIn.getName();
- }
-
- public String toString() {
- return String.format("rcvd_in=%s, proc_in=%s",
- receivedInState, processedInState);
- }
- }
-
- // TODO: extract out into CollectionUtils.
- static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
- for (T t : coll) {
- if (fn.test(t)) {
- return true;
- }
- }
- return false;
- }
-
- static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
- return !any(coll, not(fn));
- }
-
- static <T> Predicate<T> not(Predicate<T> fn) {
- return (t) -> !fn.test(t);
- }
-
- static <T> String join(String delimiter, Collection<T> coll) {
- return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
- }
-
- static <T> T find(Iterable<T> coll, Predicate<T> fn) {
- for (T t: coll) {
- if (fn.test(t)) {
- return t;
- }
- }
- return null;
- }
-
- static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
- return coll.stream().filter(fn).collect(Collectors.toList());
+ super.startProvisioning((IpClient.ProvisioningConfiguration) req);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
index 5d1d07b..daaad7a8 100644
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
@@ -98,14 +98,14 @@
public void testWrapPreferVendor() throws Exception {
initForInstances(VENDOR, HEALTHD);
mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
- verify(mCallback).onRegistration(same(mMockedHal), eq(VENDOR));
+ verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
}
@SmallTest
public void testUseHealthd() throws Exception {
initForInstances(HEALTHD);
mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
- verify(mCallback).onRegistration(same(mMockedHal), eq(HEALTHD));
+ verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
}
@SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 6b93d0e..60a55fa 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -77,7 +77,7 @@
@Test
public void testRestoringInvalidTask() throws Exception {
TaskRecord task = mSupervisor.anyTaskForIdLocked(0 /*taskId*/,
- MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null);
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
assertNull(task);
}
@@ -116,9 +116,9 @@
mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds,
0f /*aspectRatio*/, false, "secondMove");
- // Need to get pinned stack again as a new instance might have been created.
+ // Need to get stacks again as a new instance might have been created.
pinnedStack = display.getPinnedStack();
-
+ mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
// Ensure stacks have swapped tasks.
ensureStackPlacement(pinnedStack, secondTask);
ensureStackPlacement(mFullscreenStack, firstTask);
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index f9d7f9d4..4c1d3e9 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -189,6 +189,8 @@
// THEN lock task mode should be started
verifyLockTaskStarted(STATUS_BAR_MASK_PINNED);
+ // THEN screen pinning toast should be shown
+ verify(mLockTaskNotify).showPinningStartToast();
}
@Test
@@ -255,8 +257,6 @@
// WHEN system calls stopLockTaskMode
mLockTaskController.stopLockTaskMode(true, SYSTEM_UID);
- // THEN a lock tash toast should be shown
- verify(mLockTaskNotify).showToast(LOCK_TASK_MODE_LOCKED);
// THEN lock task mode should still be active
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
}
@@ -302,6 +302,8 @@
verifyLockTaskStopped(times(1));
// THEN the keyguard should be shown
verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL);
+ // THEN screen pinning toast should be shown
+ verify(mLockTaskNotify).showPinningExitToast();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 56a3fb0..b8e8946 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -89,6 +89,9 @@
public void setUp() throws Exception {
if (!sOneTimeSetupDone) {
sOneTimeSetupDone = true;
+
+ // Allows to mock package local classes and methods
+ System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
}
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 9e02399..c8b4776 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -19,7 +19,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
import android.util.Log;
import android.content.res.Resources;
@@ -429,6 +428,15 @@
}
/**
+ * Fix {@link #isGsm} based on the signal strength data.
+ *
+ * @hide
+ */
+ public void fixType() {
+ isGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ }
+
+ /**
* @param true - Gsm, Lte phones
* false - Cdma phones
*
@@ -541,30 +549,7 @@
* while 4 represents a very strong signal strength.
*/
public int getLevel() {
- int level = 0;
-
- if (isGsm) {
- level = getLteLevel();
- if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- level = getTdScdmaLevel();
- if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- level = getGsmLevel();
- }
- }
- } else {
- int cdmaLevel = getCdmaLevel();
- int evdoLevel = getEvdoLevel();
- if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- /* We don't know evdo, use cdma */
- level = cdmaLevel;
- } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- /* We don't know cdma, use evdo */
- level = evdoLevel;
- } else {
- /* We know both, use the lowest level */
- level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
- }
- }
+ int level = isGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
if (DBG) log("getLevel=" + level);
return level;
}
@@ -1049,6 +1034,36 @@
+ " " + (isGsm ? "gsm|lte" : "cdma"));
}
+ /** Returns the signal strength related to GSM. */
+ private int getGsmRelatedSignalStrength() {
+ int level = getLteLevel();
+ if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ level = getTdScdmaLevel();
+ if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ level = getGsmLevel();
+ }
+ }
+ return level;
+ }
+
+ /** Returns the signal strength related to CDMA. */
+ private int getCdmaRelatedSignalStrength() {
+ int level;
+ int cdmaLevel = getCdmaLevel();
+ int evdoLevel = getEvdoLevel();
+ if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ /* We don't know evdo, use cdma */
+ level = cdmaLevel;
+ } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ /* We don't know cdma, use evdo */
+ level = evdoLevel;
+ } else {
+ /* We know both, use the lowest level */
+ level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
+ }
+ return level;
+ }
+
/**
* Set SignalStrength based on intent notifier map
*
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index c3b2c48..9ccdd56 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -118,14 +118,8 @@
}
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, subscriptionId);
- }
- }, 0);
- return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
+ int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
@Override
public void onError(int errorCode, String message) {
try {
@@ -153,6 +147,17 @@
}
}
});
+
+ if (result == MbmsErrors.SUCCESS) {
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+ }
+
+ return result;
}
/**
@@ -251,17 +256,6 @@
throw new NullPointerException("Callback must not be null");
}
- DeathRecipient deathRecipient = new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
- mDownloadCallbackBinderMap.remove(callback.asBinder());
- mDownloadCallbackDeathRecipients.remove(callback.asBinder());
- }
- };
- mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
- callback.asBinder().linkToDeath(deathRecipient, 0);
-
DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
@Override
protected void onRemoteException(RemoteException e) {
@@ -269,9 +263,23 @@
}
};
- mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+ int result = registerStateCallback(downloadRequest, exposedCallback);
- return registerStateCallback(downloadRequest, exposedCallback);
+ if (result == MbmsErrors.SUCCESS) {
+ DeathRecipient deathRecipient = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ mDownloadCallbackBinderMap.remove(callback.asBinder());
+ mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+ }
+ };
+ mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+ mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+ }
+
+ return result;
}
/**
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 65b726d..a238153 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -70,14 +70,8 @@
}
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, subscriptionId);
- }
- }, 0);
- return initialize(new MbmsStreamingSessionCallback() {
+ int result = initialize(new MbmsStreamingSessionCallback() {
@Override
public void onError(final int errorCode, final String message) {
try {
@@ -105,6 +99,17 @@
}
}
}, subscriptionId);
+
+ if (result == MbmsErrors.SUCCESS) {
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+ }
+
+ return result;
}
@@ -161,14 +166,8 @@
}
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
- @Override
- public void binderDied() {
- onAppCallbackDied(uid, subscriptionId);
- }
- }, 0);
- return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+ int result = startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
@Override
public void onError(final int errorCode, final String message) {
try {
@@ -215,6 +214,17 @@
}
}
});
+
+ if (result == MbmsErrors.SUCCESS) {
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+ }
+
+ return result;
}
/**
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 25bfa53..047be16 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -17,6 +17,7 @@
package android.security.net.config;
import android.app.Activity;
+import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.test.ActivityUnitTestCase;
import android.util.ArraySet;
@@ -227,7 +228,8 @@
public void testConfigBuilderUsesParents() throws Exception {
// Check that a builder with a parent uses the parent's values when non is set.
NetworkSecurityConfig config = new NetworkSecurityConfig.Builder()
- .setParent(NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1))
+ .setParent(NetworkSecurityConfig
+ .getDefaultBuilder(TestUtils.makeApplicationInfo()))
.build();
assert(!config.getTrustAnchors().isEmpty());
}
@@ -268,11 +270,22 @@
// Install the test CA.
store.installCertificate(TEST_CA_CERT);
NetworkSecurityConfig preNConfig =
- NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.M, 1).build();
+ NetworkSecurityConfig
+ .getDefaultBuilder(TestUtils.makeApplicationInfo(Build.VERSION_CODES.M))
+ .build();
NetworkSecurityConfig nConfig =
- NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1).build();
+ NetworkSecurityConfig
+ .getDefaultBuilder(TestUtils.makeApplicationInfo(Build.VERSION_CODES.N))
+ .build();
+ ApplicationInfo privInfo = TestUtils.makeApplicationInfo(Build.VERSION_CODES.M);
+ privInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ NetworkSecurityConfig privConfig =
+ NetworkSecurityConfig
+ .getDefaultBuilder(privInfo)
+ .build();
Set<TrustAnchor> preNAnchors = preNConfig.getTrustAnchors();
Set<TrustAnchor> nAnchors = nConfig.getTrustAnchors();
+ Set<TrustAnchor> privAnchors = privConfig.getTrustAnchors();
Set<X509Certificate> preNCerts = new HashSet<X509Certificate>();
for (TrustAnchor anchor : preNAnchors) {
preNCerts.add(anchor.certificate);
@@ -281,8 +294,13 @@
for (TrustAnchor anchor : nAnchors) {
nCerts.add(anchor.certificate);
}
+ Set<X509Certificate> privCerts = new HashSet<X509Certificate>();
+ for (TrustAnchor anchor : privAnchors) {
+ privCerts.add(anchor.certificate);
+ }
assertTrue(preNCerts.contains(TEST_CA_CERT));
assertFalse(nCerts.contains(TEST_CA_CERT));
+ assertFalse(privCerts.contains(TEST_CA_CERT));
} finally {
// Delete the user added CA. We don't know the alias so just delete them all.
for (String alias : store.aliases()) {
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
index f7590fd..9dec21b 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -16,6 +16,8 @@
package android.security.net.config;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
import java.net.Socket;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
@@ -77,4 +79,17 @@
context.init(null, tmf.getTrustManagers(), null);
return context;
}
+
+ public static ApplicationInfo makeApplicationInfo() {
+ ApplicationInfo info = new ApplicationInfo();
+ info.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ info.targetSandboxVersion = 1;
+ return info;
+ }
+
+ public static ApplicationInfo makeApplicationInfo(int targetSdkVersion) {
+ ApplicationInfo info = makeApplicationInfo();
+ info.targetSdkVersion = targetSdkVersion;
+ return info;
+ }
}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index f7066a6..4b7a014 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -17,6 +17,7 @@
package android.security.net.config;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.util.ArraySet;
@@ -44,7 +45,8 @@
private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
public void testEmptyConfigFile() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -63,7 +65,8 @@
}
public void testEmptyAnchors() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -81,7 +84,8 @@
}
public void testBasicDomainConfig() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -117,7 +121,8 @@
}
public void testBasicPinning() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
@@ -132,7 +137,8 @@
}
public void testExpiredPin() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
@@ -146,7 +152,8 @@
}
public void testOverridesPins() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
@@ -160,7 +167,8 @@
}
public void testBadPin() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
@@ -175,7 +183,8 @@
}
public void testMultipleDomains() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -196,7 +205,8 @@
}
public void testMultipleDomainConfigs() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Should be two different config objects
@@ -211,7 +221,8 @@
}
public void testIncludeSubdomains() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Try connections.
@@ -224,7 +235,8 @@
}
public void testAttributes() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -233,7 +245,8 @@
}
public void testResourcePemCertificateSource() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -249,7 +262,8 @@
}
public void testResourceDerCertificateSource() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -265,7 +279,8 @@
}
public void testNestedDomainConfigs() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
@@ -283,7 +298,8 @@
}
public void testNestedDomainConfigsOverride() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
@@ -294,7 +310,8 @@
}
public void testDebugOverridesDisabled() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -305,7 +322,9 @@
}
public void testBasicDebugOverrides() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
+ ApplicationInfo info = TestUtils.makeApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, info);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -319,7 +338,9 @@
}
public void testDebugOverridesWithDomain() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+ ApplicationInfo info = TestUtils.makeApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, info);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -337,7 +358,9 @@
}
public void testDebugInherit() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+ ApplicationInfo info = TestUtils.makeApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, info);
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -357,7 +380,8 @@
private void testBadConfig(int configId) throws Exception {
try {
- XmlConfigSource source = new XmlConfigSource(getContext(), configId);
+ XmlConfigSource source = new XmlConfigSource(getContext(), configId,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
appConfig.getConfigForHostname("android.com");
fail("Bad config " + getContext().getResources().getResourceName(configId)
@@ -393,7 +417,8 @@
}
public void testTrustManagerKeystore() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
Provider provider = new NetworkSecurityConfigProvider();
TrustManagerFactory tmf =
@@ -415,7 +440,9 @@
}
public void testDebugDedup() throws Exception {
- XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
+ ApplicationInfo info = TestUtils.makeApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, info);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
// Check android.com.
@@ -433,15 +460,18 @@
}
public void testExtraDebugResource() throws Exception {
+ ApplicationInfo info = TestUtils.makeApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
XmlConfigSource source =
- new XmlConfigSource(getContext(), R.xml.extra_debug_resource, true);
+ new XmlConfigSource(getContext(), R.xml.extra_debug_resource, info);
ApplicationConfig appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
MoreAsserts.assertNotEmpty(config.getTrustAnchors());
// Check that the _debug file is ignored if debug is false.
- source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource, false);
+ source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource,
+ TestUtils.makeApplicationInfo());
appConfig = new ApplicationConfig(source);
assertFalse(appConfig.hasPerDomainConfigs());
config = appConfig.getConfigForHostname("");
@@ -451,12 +481,15 @@
public void testExtraDebugResourceIgnored() throws Exception {
// Verify that parsing the extra debug config resource fails only when debugging is true.
XmlConfigSource source =
- new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, false);
+ new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
// Force parsing the config file.
appConfig.getConfigForHostname("");
- source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, true);
+ ApplicationInfo info = TestUtils.makeApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, info);
appConfig = new ApplicationConfig(source);
try {
appConfig.getConfigForHostname("");
@@ -467,7 +500,8 @@
public void testDomainWhitespaceTrimming() throws Exception {
XmlConfigSource source =
- new XmlConfigSource(getContext(), R.xml.domain_whitespace, false);
+ new XmlConfigSource(getContext(), R.xml.domain_whitespace,
+ TestUtils.makeApplicationInfo());
ApplicationConfig appConfig = new ApplicationConfig(source);
NetworkSecurityConfig defaultConfig = appConfig.getConfigForHostname("");
MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("developer.android.com"));
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 9057a10..b4b8094 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -17,10 +17,12 @@
package com.android.server;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,7 +39,6 @@
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.support.test.filters.SmallTest;
-import android.system.OsConstants;
import java.net.Socket;
import java.util.Arrays;
@@ -53,8 +54,8 @@
@RunWith(Parameterized.class)
public class IpSecServiceParameterizedTest {
- private static final int DROID_SPI = 0xD1201D;
- private static final int DROID_SPI2 = DROID_SPI + 1;
+ private static final int TEST_SPI_OUT = 0xD1201D;
+ private static final int TEST_SPI_IN = TEST_SPI_OUT + 1;
private final String mRemoteAddr;
@@ -81,6 +82,16 @@
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
IpSecService mIpSecService;
+ private static final IpSecAlgorithm AUTH_ALGO =
+ new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
+ private static final IpSecAlgorithm CRYPT_ALGO =
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
+ private static final IpSecAlgorithm AEAD_ALGO =
+ new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, CRYPT_KEY, CRYPT_KEY.length * 4);
+
+ private static final int[] DIRECTIONS =
+ new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
+
public IpSecServiceParameterizedTest(String remoteAddr) {
mRemoteAddr = remoteAddr;
}
@@ -103,14 +114,14 @@
eq(IpSecTransform.DIRECTION_OUT),
anyString(),
eq(mRemoteAddr),
- eq(DROID_SPI)))
- .thenReturn(DROID_SPI);
+ eq(TEST_SPI_OUT)))
+ .thenReturn(TEST_SPI_OUT);
IpSecSpiResponse spiResp =
mIpSecService.reserveSecurityParameterIndex(
- IpSecTransform.DIRECTION_OUT, mRemoteAddr, DROID_SPI, new Binder());
+ IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
assertEquals(IpSecManager.Status.OK, spiResp.status);
- assertEquals(DROID_SPI, spiResp.spi);
+ assertEquals(TEST_SPI_OUT, spiResp.spi);
}
@Test
@@ -120,56 +131,60 @@
eq(IpSecTransform.DIRECTION_OUT),
anyString(),
eq(mRemoteAddr),
- eq(DROID_SPI)))
- .thenReturn(DROID_SPI);
+ eq(TEST_SPI_OUT)))
+ .thenReturn(TEST_SPI_OUT);
IpSecSpiResponse spiResp =
mIpSecService.reserveSecurityParameterIndex(
- IpSecTransform.DIRECTION_OUT, mRemoteAddr, DROID_SPI, new Binder());
+ IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(spiResp.resourceId), anyInt(), anyString(), anyString(), eq(DROID_SPI));
+ eq(spiResp.resourceId),
+ anyInt(),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI_OUT));
}
- IpSecConfig buildIpSecConfig() throws Exception {
- IpSecManager ipSecManager = new IpSecManager(mIpSecService);
-
- // Mocking the netd to allocate SPI
+ private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi)
+ throws Exception {
when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
- .thenReturn(DROID_SPI)
- .thenReturn(DROID_SPI2);
+ .thenReturn(returnSpi);
- IpSecAlgorithm encryptAlgo = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
- IpSecAlgorithm authAlgo =
- new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 8);
+ IpSecSpiResponse spi =
+ mIpSecService.reserveSecurityParameterIndex(
+ direction,
+ NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(),
+ IpSecManager.INVALID_SECURITY_PARAMETER_INDEX,
+ new Binder());
+ return spi.resourceId;
+ }
- /** Allocate and add SPI records in the IpSecService through IpSecManager interface. */
- IpSecManager.SecurityParameterIndex outSpi =
- ipSecManager.reserveSecurityParameterIndex(
- IpSecTransform.DIRECTION_OUT,
- NetworkUtils.numericToInetAddress(mRemoteAddr));
- IpSecManager.SecurityParameterIndex inSpi =
- ipSecManager.reserveSecurityParameterIndex(
- IpSecTransform.DIRECTION_IN,
- NetworkUtils.numericToInetAddress(mRemoteAddr));
-
- IpSecConfig config = new IpSecConfig();
- config.setSpiResourceId(IpSecTransform.DIRECTION_IN, inSpi.getResourceId());
- config.setSpiResourceId(IpSecTransform.DIRECTION_OUT, outSpi.getResourceId());
- config.setEncryption(IpSecTransform.DIRECTION_OUT, encryptAlgo);
- config.setAuthentication(IpSecTransform.DIRECTION_OUT, authAlgo);
- config.setEncryption(IpSecTransform.DIRECTION_IN, encryptAlgo);
- config.setAuthentication(IpSecTransform.DIRECTION_IN, authAlgo);
+ private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception {
+ config.setSpiResourceId(
+ IpSecTransform.DIRECTION_OUT,
+ getNewSpiResourceId(IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT));
+ config.setSpiResourceId(
+ IpSecTransform.DIRECTION_IN,
+ getNewSpiResourceId(IpSecTransform.DIRECTION_IN, mRemoteAddr, TEST_SPI_IN));
config.setRemoteAddress(mRemoteAddr);
- return config;
+ }
+
+ private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception {
+ for (int direction : DIRECTIONS) {
+ config.setEncryption(direction, CRYPT_ALGO);
+ config.setAuthentication(direction, AUTH_ALGO);
+ }
}
@Test
public void testCreateTransportModeTransform() throws Exception {
- IpSecConfig ipSecConfig = buildIpSecConfig();
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -183,13 +198,72 @@
anyString(),
anyString(),
anyLong(),
- eq(DROID_SPI),
+ eq(TEST_SPI_OUT),
eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
eq(AUTH_KEY),
anyInt(),
eq(IpSecAlgorithm.CRYPT_AES_CBC),
eq(CRYPT_KEY),
anyInt(),
+ eq(""),
+ isNull(),
+ eq(0),
+ anyInt(),
+ anyInt(),
+ anyInt());
+ verify(mMockNetd)
+ .ipSecAddSecurityAssociation(
+ eq(createTransformResp.resourceId),
+ anyInt(),
+ eq(IpSecTransform.DIRECTION_IN),
+ anyString(),
+ anyString(),
+ anyLong(),
+ eq(TEST_SPI_IN),
+ eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
+ eq(AUTH_KEY),
+ anyInt(),
+ eq(IpSecAlgorithm.CRYPT_AES_CBC),
+ eq(CRYPT_KEY),
+ anyInt(),
+ eq(""),
+ isNull(),
+ eq(0),
+ anyInt(),
+ anyInt(),
+ anyInt());
+ }
+
+ @Test
+ public void testCreateTransportModeTransformAead() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+ ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO);
+ ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+ assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+ verify(mMockNetd)
+ .ipSecAddSecurityAssociation(
+ eq(createTransformResp.resourceId),
+ anyInt(),
+ eq(IpSecTransform.DIRECTION_OUT),
+ anyString(),
+ anyString(),
+ anyLong(),
+ eq(TEST_SPI_OUT),
+ eq(""),
+ isNull(),
+ eq(0),
+ eq(""),
+ isNull(),
+ eq(0),
+ eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
+ eq(CRYPT_KEY),
+ anyInt(),
anyInt(),
anyInt(),
anyInt());
@@ -201,11 +275,14 @@
anyString(),
anyString(),
anyLong(),
- eq(DROID_SPI2),
- eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
- eq(AUTH_KEY),
- anyInt(),
- eq(IpSecAlgorithm.CRYPT_AES_CBC),
+ eq(TEST_SPI_IN),
+ eq(""),
+ isNull(),
+ eq(0),
+ eq(""),
+ isNull(),
+ eq(0),
+ eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
eq(CRYPT_KEY),
anyInt(),
anyInt(),
@@ -214,8 +291,68 @@
}
@Test
+ public void testCreateInvalidConfigAeadWithAuth() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+ for (int direction : DIRECTIONS) {
+ ipSecConfig.setAuthentication(direction, AUTH_ALGO);
+ ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+ }
+
+ try {
+ mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+ fail(
+ "IpSecService should have thrown an error on authentication being"
+ + " enabled with authenticated encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testCreateInvalidConfigAeadWithCrypt() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+ for (int direction : DIRECTIONS) {
+ ipSecConfig.setEncryption(direction, CRYPT_ALGO);
+ ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+ }
+
+ try {
+ mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+ fail(
+ "IpSecService should have thrown an error on encryption being"
+ + " enabled with authenticated encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testCreateInvalidConfigAeadWithAuthAndCrypt() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+ for (int direction : DIRECTIONS) {
+ ipSecConfig.setAuthentication(direction, AUTH_ALGO);
+ ipSecConfig.setEncryption(direction, CRYPT_ALGO);
+ ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+ }
+
+ try {
+ mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+ fail(
+ "IpSecService should have thrown an error on authentication and encryption being"
+ + " enabled with authenticated encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testDeleteTransportModeTransform() throws Exception {
- IpSecConfig ipSecConfig = buildIpSecConfig();
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -227,19 +364,21 @@
eq(IpSecTransform.DIRECTION_OUT),
anyString(),
anyString(),
- eq(DROID_SPI));
+ eq(TEST_SPI_OUT));
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
eq(createTransformResp.resourceId),
eq(IpSecTransform.DIRECTION_IN),
anyString(),
anyString(),
- eq(DROID_SPI2));
+ eq(TEST_SPI_IN));
}
@Test
public void testApplyTransportModeTransform() throws Exception {
- IpSecConfig ipSecConfig = buildIpSecConfig();
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -255,7 +394,7 @@
eq(IpSecTransform.DIRECTION_OUT),
anyString(),
anyString(),
- eq(DROID_SPI));
+ eq(TEST_SPI_OUT));
verify(mMockNetd)
.ipSecApplyTransportModeTransform(
eq(pfd.getFileDescriptor()),
@@ -263,7 +402,7 @@
eq(IpSecTransform.DIRECTION_IN),
anyString(),
anyString(),
- eq(DROID_SPI2));
+ eq(TEST_SPI_IN));
}
@Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index ff51d51..058504d 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -84,17 +84,18 @@
"filter/AbiFilter.cpp",
"filter/ConfigFilter.cpp",
"format/Archive.cpp",
+ "format/Container.cpp",
"format/binary/BinaryResourceParser.cpp",
"format/binary/ResChunkPullParser.cpp",
"format/binary/TableFlattener.cpp",
"format/binary/XmlFlattener.cpp",
"format/proto/ProtoDeserialize.cpp",
"format/proto/ProtoSerialize.cpp",
- "io/BigBufferStreams.cpp",
+ "io/BigBufferStream.cpp",
"io/File.cpp",
- "io/FileInputStream.cpp",
+ "io/FileStream.cpp",
"io/FileSystem.cpp",
- "io/StringInputStream.cpp",
+ "io/StringStream.cpp",
"io/Util.cpp",
"io/ZipArchive.cpp",
"link/AutoVersioner.cpp",
@@ -167,7 +168,7 @@
"test/Builders.cpp",
"test/Common.cpp",
"**/*_test.cpp",
- ],
+ ] + toolSources,
static_libs: [
"libaapt2",
"libgmock",
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 1555d6126..61c304b 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -35,11 +35,11 @@
namespace {
-class PrintVisitor : public DescendingValueVisitor {
+class PrintVisitor : public ConstValueVisitor {
public:
- using DescendingValueVisitor::Visit;
+ using ConstValueVisitor::Visit;
- void Visit(Attribute* attr) override {
+ void Visit(const Attribute* attr) override {
std::cout << "(attr) type=";
attr->PrintMask(&std::cout);
static constexpr uint32_t kMask =
@@ -55,7 +55,7 @@
}
}
- void Visit(Style* style) override {
+ void Visit(const Style* style) override {
std::cout << "(style)";
if (style->parent) {
const Reference& parent_ref = style->parent.value();
@@ -90,15 +90,15 @@
}
}
- void Visit(Array* array) override {
+ void Visit(const Array* array) override {
array->Print(&std::cout);
}
- void Visit(Plural* plural) override {
+ void Visit(const Plural* plural) override {
plural->Print(&std::cout);
}
- void Visit(Styleable* styleable) override {
+ void Visit(const Styleable* styleable) override {
std::cout << "(styleable)";
for (const auto& attr : styleable->entries) {
std::cout << "\n ";
@@ -116,17 +116,17 @@
}
}
- void VisitItem(Item* item) override {
+ void VisitItem(const Item* item) override {
item->Print(&std::cout);
}
};
} // namespace
-void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) {
+void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options) {
PrintVisitor visitor;
- for (auto& package : table->packages) {
+ for (const auto& package : table.packages) {
std::cout << "Package name=" << package->name;
if (package->id) {
std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
@@ -261,11 +261,11 @@
namespace {
-class XmlPrinter : public xml::Visitor {
+class XmlPrinter : public xml::ConstVisitor {
public:
- using xml::Visitor::Visit;
+ using xml::ConstVisitor::Visit;
- void Visit(xml::Element* el) override {
+ void Visit(const xml::Element* el) override {
const size_t previous_size = prefix_.size();
for (const xml::NamespaceDecl& decl : el->namespace_decls) {
@@ -301,11 +301,11 @@
}
prefix_ += " ";
- xml::Visitor::Visit(el);
+ xml::ConstVisitor::Visit(el);
prefix_.resize(previous_size);
}
- void Visit(xml::Text* text) override {
+ void Visit(const xml::Text* text) override {
std::cerr << prefix_ << "T: '" << text->text << "'\n";
}
@@ -315,9 +315,9 @@
} // namespace
-void Debug::DumpXml(xml::XmlResource* doc) {
+void Debug::DumpXml(const xml::XmlResource& doc) {
XmlPrinter printer;
- doc->root->Accept(&printer);
+ doc.root->Accept(&printer);
}
} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index e2456c7..296d04b 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -31,13 +31,11 @@
};
struct Debug {
- static void PrintTable(ResourceTable* table,
- const DebugPrintTableOptions& options = {});
+ static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options = {});
static void PrintStyleGraph(ResourceTable* table,
const ResourceName& target_style);
static void DumpHex(const void* data, size_t len);
- static void DumpXml(xml::XmlResource* doc);
- static std::string ToString(xml::XmlResource* doc);
+ static void DumpXml(const xml::XmlResource& doc);
};
} // namespace aapt
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index c1815c8..ae32ee9 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -21,7 +21,7 @@
#include "format/Archive.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
-#include "io/BigBufferInputStream.h"
+#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "xml/XmlDom.h"
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index cbcc8fb..87b9867 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -157,12 +157,22 @@
};
struct ResourceFile {
+ enum class Type {
+ kUnknown,
+ kPng,
+ kBinaryXml,
+ kProtoXml,
+ };
+
// Name
ResourceName name;
// Configuration
ConfigDescription config;
+ // Type
+ Type type;
+
// Source
Source source;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index f08b03e..9a5cd3e 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -22,7 +22,7 @@
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
#include "test/Test.h"
#include "xml/XmlPullParser.h"
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 6fac6e9..24187d9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -704,8 +704,15 @@
} else {
if (type != ResourceType::kString && util::StartsWith(str, "res/")) {
// This must be a FileReference.
- return util::make_unique<FileReference>(dst_pool->MakeRef(
- str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+ std::unique_ptr<FileReference> file_ref =
+ util::make_unique<FileReference>(dst_pool->MakeRef(
+ str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+ if (util::EndsWith(*file_ref->path, ".xml")) {
+ file_ref->type = ResourceFile::Type::kBinaryXml;
+ } else if (util::EndsWith(*file_ref->path, ".png")) {
+ file_ref->type = ResourceFile::Type::kPng;
+ }
+ return std::move(file_ref);
}
// There are no styles associated with this string, so treat it as a simple string.
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index e013729..082fd86 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -65,7 +65,7 @@
}
RawString* RawString::Clone(StringPool* new_pool) const {
- RawString* rs = new RawString(new_pool->MakeRef(*value));
+ RawString* rs = new RawString(new_pool->MakeRef(value));
rs->comment_ = comment_;
rs->source_ = source_;
return rs;
@@ -207,7 +207,7 @@
}
String* String::Clone(StringPool* new_pool) const {
- String* str = new String(new_pool->MakeRef(*value));
+ String* str = new String(new_pool->MakeRef(value));
str->comment_ = comment_;
str->source_ = source_;
str->untranslatable_sections = untranslatable_sections;
@@ -290,8 +290,9 @@
}
FileReference* FileReference::Clone(StringPool* new_pool) const {
- FileReference* fr = new FileReference(new_pool->MakeRef(*path));
+ FileReference* fr = new FileReference(new_pool->MakeRef(path));
fr->file = file;
+ fr->type = type;
fr->comment_ = comment_;
fr->source_ = source_;
return fr;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 742765d..fd242a1 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -249,6 +249,10 @@
// This field is NOT persisted in any format. It is transient.
io::IFile* file = nullptr;
+ // FileType of the file pointed to by `file`. This is used to know how to inflate the file,
+ // or if to inflate at all (just copy).
+ ResourceFile::Type type = ResourceFile::Type::kUnknown;
+
FileReference() = default;
explicit FileReference(const StringPool::Ref& path);
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index 10f9b55..a80a9dc 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -18,6 +18,10 @@
#include "test/Test.h"
+using ::testing::Eq;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
namespace aapt {
TEST(ResourceValuesTest, PluralEquals) {
@@ -148,6 +152,22 @@
EXPECT_TRUE(a->Equals(b.get()));
}
+TEST(ResourcesValuesTest, StringClones) {
+ StringPool pool_a;
+ StringPool pool_b;
+
+ String str_a(pool_a.MakeRef("hello", StringPool::Context(test::ParseConfigOrDie("en"))));
+
+ ASSERT_THAT(pool_a, SizeIs(1u));
+ EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
+ EXPECT_THAT(pool_a.strings()[0]->value, StrEq("hello"));
+
+ std::unique_ptr<String> str_b(str_a.Clone(&pool_b));
+ ASSERT_THAT(pool_b, SizeIs(1u));
+ EXPECT_THAT(pool_b.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
+ EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello"));
+}
+
TEST(ResourceValuesTest, StyleMerges) {
StringPool pool_a;
StringPool pool_b;
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 174b7f6..7e7c86d 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -269,8 +269,19 @@
// A value that is a reference to an external entity, like an XML file or a PNG.
message FileReference {
+ enum Type {
+ UNKNOWN = 0;
+ PNG = 1;
+ BINARY_XML = 2;
+ PROTO_XML = 3;
+ }
+
// Path to a file within the APK (typically res/type-config/entry.ext).
string path = 1;
+
+ // The type of file this path points to. For UAM bundle, this cannot be
+ // BINARY_XML.
+ Type type = 2;
}
// A value that represents a primitive data type (float, int, boolean, etc.).
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index 0b0a252..520b242 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -41,13 +41,13 @@
// The configuration for which the resource is defined.
aapt.pb.Configuration config = 2;
+ // The type of the file.
+ aapt.pb.FileReference.Type type = 3;
+
// The filesystem path to where the source file originated.
// Mainly used to display helpful error messages.
- string source_path = 3;
+ string source_path = 4;
// Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
- repeated Symbol exported_symbol = 4;
-
- // If this is a compiled XML file, this is the root node.
- aapt.pb.XmlNode xml_root = 5;
+ repeated Symbol exported_symbol = 5;
}
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 705b1ab..3a1a18c 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -191,6 +191,13 @@
return Ref(borrow);
}
+StringPool::Ref StringPool::MakeRef(const Ref& ref) {
+ if (ref.entry_->pool_ == this) {
+ return ref;
+ }
+ return MakeRef(ref.entry_->value, ref.entry_->context);
+}
+
StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
return MakeRef(str, Context{});
}
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 8350d0d..3c1f3dc 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -49,6 +49,8 @@
// Otherwise, the style data array would have to be sparse and take up more space.
class StringPool {
public:
+ using size_type = size_t;
+
class Context {
public:
enum : uint32_t {
@@ -165,6 +167,9 @@
// when sorting the string pool. Returns a reference to the string in the pool.
Ref MakeRef(const android::StringPiece& str, const Context& context);
+ // Adds a string from another string pool. Returns a reference to the string in the string pool.
+ Ref MakeRef(const Ref& ref);
+
// Adds a style to the string pool and returns a reference to it.
StyleRef MakeRef(const StyleString& str);
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index a5e6aef..53910af 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -36,10 +36,11 @@
#include "compile/PseudolocaleGenerator.h"
#include "compile/XmlIdCollector.h"
#include "format/Archive.h"
-#include "format/binary/XmlFlattener.h"
+#include "format/Container.h"
#include "format/proto/ProtoSerialize.h"
-#include "io/BigBufferOutputStream.h"
-#include "io/FileInputStream.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
+#include "io/StringStream.h"
#include "io/Util.h"
#include "util/Files.h"
#include "util/Maybe.h"
@@ -49,6 +50,7 @@
using ::aapt::io::FileInputStream;
using ::android::StringPiece;
+using ::android::base::SystemErrorCodeToString;
using ::google::protobuf::io::CopyingOutputStreamAdaptor;
namespace aapt {
@@ -116,7 +118,7 @@
bool verbose = false;
};
-static std::string BuildIntermediateFilename(const ResourcePathData& data) {
+static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
std::stringstream name;
name << data.resource_dir;
if (!data.config_str.empty()) {
@@ -141,7 +143,7 @@
std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
if (!d) {
context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
- << android::base::SystemErrorCodeToString(errno));
+ << SystemErrorCodeToString(errno));
return false;
}
@@ -160,7 +162,7 @@
std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
if (!subdir) {
context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
- << android::base::SystemErrorCodeToString(errno));
+ << SystemErrorCodeToString(errno));
return false;
}
@@ -241,16 +243,15 @@
return false;
}
- // Make sure CopyingOutputStreamAdaptor is deleted before we call
- // writer->FinishEntry().
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
{
- // Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream interface.
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
+ ContainerWriter container_writer(©ing_adaptor, 1u);
pb::ResourceTable pb_table;
SerializeTableToPb(table, &pb_table);
- if (!pb_table.SerializeToZeroCopyStream(©ing_adaptor)) {
+ if (!container_writer.AddResTableEntry(pb_table)) {
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
return false;
}
@@ -263,46 +264,8 @@
return true;
}
-static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
- const BigBuffer& buffer, IArchiveWriter* writer,
- IDiagnostics* diag) {
- // Start the entry so we can write the header.
- if (!writer->StartEntry(output_path, 0)) {
- diag->Error(DiagMessage(output_path) << "failed to open file");
- return false;
- }
-
- // Make sure CopyingOutputStreamAdaptor is deleted before we call
- // writer->FinishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream interface.
- CopyingOutputStreamAdaptor copying_adaptor(writer);
- CompiledFileOutputStream output_stream(©ing_adaptor);
-
- // Number of CompiledFiles.
- output_stream.WriteLittleEndian32(1);
-
- pb::internal::CompiledFile pb_compiled_file;
- SerializeCompiledFileToPb(file, &pb_compiled_file);
- output_stream.WriteCompiledFile(pb_compiled_file);
- output_stream.WriteData(buffer);
-
- if (output_stream.HadError()) {
- diag->Error(DiagMessage(output_path) << "failed to write data");
- return false;
- }
- }
-
- if (!writer->FinishEntry()) {
- diag->Error(DiagMessage(output_path) << "failed to finish writing data");
- return false;
- }
- return true;
-}
-
-static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
- const android::FileMap& map, IArchiveWriter* writer,
+static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+ io::KnownSizeInputStream* in, IArchiveWriter* writer,
IDiagnostics* diag) {
// Start the entry so we can write the header.
if (!writer->StartEntry(output_path, 0)) {
@@ -310,24 +273,17 @@
return false;
}
- // Make sure CopyingOutputStreamAdaptor is deleted before we call
- // writer->FinishEntry().
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
{
- // Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream interface.
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
- CompiledFileOutputStream output_stream(©ing_adaptor);
-
- // Number of CompiledFiles.
- output_stream.WriteLittleEndian32(1);
+ ContainerWriter container_writer(©ing_adaptor, 1u);
pb::internal::CompiledFile pb_compiled_file;
SerializeCompiledFileToPb(file, &pb_compiled_file);
- output_stream.WriteCompiledFile(pb_compiled_file);
- output_stream.WriteData(map.getDataPtr(), map.getDataLength());
- if (output_stream.HadError()) {
- diag->Error(DiagMessage(output_path) << "failed to write data");
+ if (!container_writer.AddResFileEntry(pb_compiled_file, in)) {
+ diag->Error(DiagMessage(output_path) << "failed to write entry data");
return false;
}
}
@@ -339,23 +295,19 @@
return true;
}
-static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
- xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
- BigBuffer buffer(1024);
- XmlFlattenerOptions xml_flattener_options;
- xml_flattener_options.keep_raw_values = true;
- XmlFlattener flattener(&buffer, xml_flattener_options);
- if (!flattener.Consume(context, xmlres)) {
- return false;
- }
-
+static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+ ContainerWriter* container_writer, IDiagnostics* diag) {
pb::internal::CompiledFile pb_compiled_file;
- SerializeCompiledFileToPb(xmlres->file, &pb_compiled_file);
- out->WriteCompiledFile(pb_compiled_file);
- out->WriteData(buffer);
+ SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
- if (out->HadError()) {
- context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write XML data");
+ pb::XmlNode pb_xml_node;
+ SerializeXmlToPb(*xmlres.root, &pb_xml_node);
+
+ std::string serialized_xml = pb_xml_node.SerializeAsString();
+ io::StringInputStream serialized_in(serialized_xml);
+
+ if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) {
+ diag->Error(DiagMessage(output_path) << "failed to write entry data");
return false;
}
return true;
@@ -404,6 +356,7 @@
xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
xmlres->file.config = path_data.config;
xmlres->file.source = path_data.source;
+ xmlres->file.type = ResourceFile::Type::kProtoXml;
// Collect IDs that are defined here.
XmlIdCollector collector;
@@ -423,24 +376,23 @@
return false;
}
+ std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
+ inline_xml_format_parser.GetExtractedInlineXmlDocuments();
+
// Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
{
// Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
- CompiledFileOutputStream output_stream(©ing_adaptor);
+ ContainerWriter container_writer(©ing_adaptor, 1u + inline_documents.size());
- std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
- inline_xml_format_parser.GetExtractedInlineXmlDocuments();
-
- // Number of CompiledFiles.
- output_stream.WriteLittleEndian32(1 + inline_documents.size());
-
- if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
+ if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer,
+ context->GetDiagnostics())) {
return false;
}
- for (auto& inline_xml_doc : inline_documents) {
- if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
+ for (const std::unique_ptr<xml::XmlResource>& inline_xml_doc : inline_documents) {
+ if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer,
+ context->GetDiagnostics())) {
return false;
}
}
@@ -465,6 +417,7 @@
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
res_file.config = path_data.config;
res_file.source = path_data.source;
+ res_file.type = ResourceFile::Type::kPng;
{
std::string content;
@@ -472,7 +425,7 @@
true /*follow_symlinks*/)) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
<< "failed to open file: "
- << android::base::SystemErrorCodeToString(errno));
+ << SystemErrorCodeToString(errno));
return false;
}
@@ -556,8 +509,9 @@
}
}
- if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
- context->GetDiagnostics())) {
+ io::BigBufferInputStream buffer_in(&buffer);
+ if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
+ context->GetDiagnostics())) {
return false;
}
return true;
@@ -575,6 +529,7 @@
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
res_file.config = path_data.config;
res_file.source = path_data.source;
+ res_file.type = ResourceFile::Type::kUnknown;
std::string error_str;
Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
@@ -584,7 +539,8 @@
return false;
}
- if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
+ io::MmappedData mmapped_in(std::move(f.value()));
+ if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer,
context->GetDiagnostics())) {
return false;
}
@@ -614,7 +570,7 @@
}
NameMangler* GetNameMangler() override {
- abort();
+ UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase";
return nullptr;
}
@@ -628,7 +584,7 @@
}
SymbolTable* GetExternalSymbols() override {
- abort();
+ UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase";
return nullptr;
}
@@ -637,14 +593,13 @@
}
private:
+ DISALLOW_COPY_AND_ASSIGN(CompileContext);
+
IDiagnostics* diagnostics_;
bool verbose_ = false;
};
-/**
- * Entry point for compilation phase. Parses arguments and dispatches to the
- * correct steps.
- */
+// Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
CompileContext context(diagnostics);
CompileOptions options;
@@ -717,50 +672,34 @@
continue;
}
+ // Determine how to compile the file based on its type.
+ auto compile_func = &CompileFile;
if (path_data.resource_dir == "values") {
- // Overwrite the extension.
+ compile_func = &CompileTable;
+ // We use a different extension (not necessary anymore, but avoids altering the existing
+ // build system logic).
path_data.extension = "arsc";
-
- const std::string output_filename = BuildIntermediateFilename(path_data);
- if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
- error = true;
- }
-
- } else {
- const std::string output_filename = BuildIntermediateFilename(path_data);
- if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
- if (*type != ResourceType::kRaw) {
- if (path_data.extension == "xml") {
- if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
- error = true;
- }
- } else if (!options.no_png_crunch &&
- (path_data.extension == "png" || path_data.extension == "9.png")) {
- if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
- error = true;
- }
- } else {
- if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
- error = true;
- }
- }
- } else {
- if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
- error = true;
- }
+ } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
+ if (*type != ResourceType::kRaw) {
+ if (path_data.extension == "xml") {
+ compile_func = &CompileXml;
+ } else if (!options.no_png_crunch &&
+ (path_data.extension == "png" || path_data.extension == "9.png")) {
+ compile_func = &CompilePng;
}
- } else {
- context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
- << "'");
- error = true;
}
+ } else {
+ context.GetDiagnostics()->Error(DiagMessage()
+ << "invalid file path '" << path_data.source << "'");
+ error = true;
+ continue;
}
- }
- if (error) {
- return 1;
+ // Compile the file.
+ const std::string out_path = BuildIntermediateContainerFilename(path_data);
+ error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path);
}
- return 0;
+ return error ? 1 : 0;
}
} // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 44032f6..090c3fb 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -21,8 +21,10 @@
#include "Debug.h"
#include "Diagnostics.h"
#include "Flags.h"
+#include "format/Container.h"
#include "format/binary/BinaryResourceParser.h"
#include "format/proto/ProtoDeserialize.h"
+#include "io/FileStream.h"
#include "io/ZipArchive.h"
#include "process/IResourceTableConsumer.h"
#include "util/Files.h"
@@ -31,42 +33,51 @@
namespace aapt {
-bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len,
- const Source& source, IAaptContext* context) {
- ResourceFile file;
- std::string error;
- if (!DeserializeCompiledFileFromPb(pb_file, &file, &error)) {
- context->GetDiagnostics()->Warn(DiagMessage(source)
- << "failed to read compiled file: " << error);
- return false;
+static const char* ResourceFileTypeToString(const ResourceFile::Type& type) {
+ switch (type) {
+ case ResourceFile::Type::kPng:
+ return "PNG";
+ case ResourceFile::Type::kBinaryXml:
+ return "BINARY_XML";
+ case ResourceFile::Type::kProtoXml:
+ return "PROTO_XML";
+ default:
+ break;
}
-
- std::cout << "Resource: " << file.name << "\n"
- << "Config: " << file.config << "\n"
- << "Source: " << file.source << "\n";
- return true;
+ return "UNKNOWN";
}
-bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset,
+ size_t len) {
+ std::cout << "Resource: " << file.name << "\n"
+ << "Config: " << file.config << "\n"
+ << "Source: " << file.source << "\n"
+ << "Type: " << ResourceFileTypeToString(file.type) << "\n"
+ << "DataOff: " << offset << "\n"
+ << "DataLen: " << len << "\n";
+}
+
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+ DebugPrintTableOptions print_options;
+ print_options.show_sources = true;
+
std::string err;
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
if (zip) {
ResourceTable table;
- if (io::IFile* file = zip->FindFile("resources.arsc.flat")) {
+ if (io::IFile* file = zip->FindFile("resources.pb")) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (data == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "failed to open resources.arsc.flat");
+ context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb");
return false;
}
pb::ResourceTable pb_table;
if (!pb_table.ParseFromArray(data->data(), data->size())) {
- context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat");
+ context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb");
return false;
}
- ResourceTable table;
if (!DeserializeTableFromPb(pb_table, &table, &err)) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to parse table: " << err);
@@ -85,62 +96,72 @@
}
}
- DebugPrintTableOptions options;
- options.show_sources = true;
- Debug::PrintTable(&table, options);
+ Debug::PrintTable(table, print_options);
return true;
}
err.clear();
- Maybe<android::FileMap> file = file::MmapPath(file_path, &err);
- if (!file) {
- context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
+ io::FileInputStream input(file_path);
+ if (input.HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "failed to open file: " << input.GetError());
return false;
}
- android::FileMap* file_map = &file.value();
-
- // Check to see if this is a loose ResourceTable.
- pb::ResourceTable pb_table;
- if (pb_table.ParseFromArray(file_map->getDataPtr(), file_map->getDataLength())) {
- ResourceTable table;
- if (DeserializeTableFromPb(pb_table, &table, &err)) {
- DebugPrintTableOptions options;
- options.show_sources = true;
- Debug::PrintTable(&table, options);
- return true;
- }
- }
-
// Try as a compiled file.
- CompiledFileInputStream input(file_map->getDataPtr(), file_map->getDataLength());
- uint32_t num_files = 0;
- if (!input.ReadLittleEndian32(&num_files)) {
+ ContainerReader reader(&input);
+ if (reader.HadError()) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "failed to read container: " << reader.GetError());
return false;
}
- for (uint32_t i = 0; i < num_files; i++) {
- pb::internal::CompiledFile compiled_file;
- if (!input.ReadCompiledFile(&compiled_file)) {
- context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
- return false;
- }
+ ContainerReaderEntry* entry;
+ while ((entry = reader.Next()) != nullptr) {
+ if (entry->Type() == ContainerEntryType::kResTable) {
+ pb::ResourceTable pb_table;
+ if (!entry->GetResTable(&pb_table)) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "failed to parse proto table: " << entry->GetError());
+ continue;
+ }
- uint64_t offset, len;
- if (!input.ReadDataMetaData(&offset, &len)) {
- context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data");
- return false;
- }
+ ResourceTable table;
+ err.clear();
+ if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+ context->GetDiagnostics()->Error(DiagMessage(file_path)
+ << "failed to parse table: " << err);
+ continue;
+ }
- const void* data = static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
- if (!DumpCompiledFile(compiled_file, data, len, Source(file_path), context)) {
- return false;
+ Debug::PrintTable(table, print_options);
+ } else if (entry->Type() == ContainerEntryType::kResFile) {
+ pb::internal::CompiledFile pb_compiled_file;
+ off64_t offset;
+ size_t length;
+ if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError());
+ continue;
+ }
+
+ ResourceFile file;
+ std::string error;
+ if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
+ context->GetDiagnostics()->Warn(DiagMessage(file_path)
+ << "failed to parse compiled file: " << error);
+ continue;
+ }
+
+ DumpCompiledFile(file, Source(file_path), offset, length);
}
}
return true;
}
+namespace {
+
class DumpContext : public IAaptContext {
public:
PackageType GetPackageType() override {
@@ -153,7 +174,7 @@
}
NameMangler* GetNameMangler() override {
- abort();
+ UNIMPLEMENTED(FATAL);
return nullptr;
}
@@ -167,7 +188,7 @@
}
SymbolTable* GetExternalSymbols() override {
- abort();
+ UNIMPLEMENTED(FATAL);
return nullptr;
}
@@ -188,9 +209,9 @@
bool verbose_ = false;
};
-/**
- * Entry point for dump command.
- */
+} // namespace
+
+// Entry point for dump command.
int Dump(const std::vector<StringPiece>& args) {
bool verbose = false;
Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
@@ -206,7 +227,6 @@
return 1;
}
}
-
return 0;
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 40d71a3..55a4c43 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -25,7 +25,6 @@
#include "android-base/file.h"
#include "android-base/stringprintf.h"
#include "androidfw/StringPiece.h"
-#include "google/protobuf/io/coded_stream.h"
#include "AppInfo.h"
#include "Debug.h"
@@ -39,13 +38,14 @@
#include "compile/IdAssigner.h"
#include "filter/ConfigFilter.h"
#include "format/Archive.h"
+#include "format/Container.h"
#include "format/binary/BinaryResourceParser.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "format/proto/ProtoSerialize.h"
-#include "io/BigBufferInputStream.h"
-#include "io/FileInputStream.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
#include "io/FileSystem.h"
#include "io/Util.h"
#include "io/ZipArchive.h"
@@ -71,6 +71,14 @@
namespace aapt {
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+
+enum class OutputFormat {
+ kApk,
+ kProto,
+};
+
struct LinkOptions {
std::string output_path;
std::string manifest_path;
@@ -79,6 +87,7 @@
std::vector<std::string> assets_dirs;
bool output_to_directory = false;
bool auto_add_overlay = false;
+ OutputFormat output_format = OutputFormat::kApk;
// Java/Proguard options.
Maybe<std::string> generate_java_class_path;
@@ -253,26 +262,39 @@
IAaptContext* context_;
};
-static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path,
- bool keep_raw_values, bool utf16, IArchiveWriter* writer) {
- BigBuffer buffer(1024);
- XmlFlattenerOptions options = {};
- options.keep_raw_values = keep_raw_values;
- options.use_utf16 = utf16;
- XmlFlattener flattener(&buffer, options);
- if (!flattener.Consume(context, xml_res)) {
- return false;
- }
-
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
+ const StringPiece& path, bool keep_raw_values, bool utf16,
+ OutputFormat format, IArchiveWriter* writer) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values="
<< (keep_raw_values ? "true" : "false")
<< ")");
}
- io::BigBufferInputStream input_stream(&buffer);
- return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
- ArchiveEntry::kCompress, writer);
+ switch (format) {
+ case OutputFormat::kApk: {
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions options = {};
+ options.keep_raw_values = keep_raw_values;
+ options.use_utf16 = utf16;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(context, &xml_res)) {
+ return false;
+ }
+
+ io::BigBufferInputStream input_stream(&buffer);
+ return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
+ ArchiveEntry::kCompress, writer);
+ } break;
+
+ case OutputFormat::kProto: {
+ pb::XmlNode pb_node;
+ SerializeXmlResourceToPb(xml_res, &pb_node);
+ return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
+ writer);
+ } break;
+ }
+ return false;
}
static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
@@ -310,6 +332,7 @@
bool keep_raw_values = false;
bool do_not_compress_anything = false;
bool update_proguard_spec = false;
+ OutputFormat output_format = OutputFormat::kApk;
std::unordered_set<std::string> extensions_to_not_compress;
};
@@ -467,6 +490,10 @@
<< "linking " << src.path << " (" << doc->file.name << ")");
}
+ // First, strip out any tools namespace attributes. AAPT stripped them out early, which means
+ // that existing projects have out-of-date references which pass compilation.
+ xml::StripAndroidStudioAttributes(doc->root.get());
+
XmlReferenceLinker xml_linker;
if (!xml_linker.Consume(context_, doc)) {
return {};
@@ -543,9 +570,9 @@
file_op.config = config_value->config;
file_op.file_to_copy = file;
- const StringPiece src_path = file->GetSource().path;
if (type->type != ResourceType::kRaw &&
- (util::EndsWith(src_path, ".xml.flat") || util::EndsWith(src_path, ".xml"))) {
+ (file_ref->type == ResourceFile::Type::kBinaryXml ||
+ file_ref->type == ResourceFile::Type::kProtoXml)) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
@@ -553,11 +580,27 @@
return false;
}
- file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
- context_->GetDiagnostics(), file->GetSource());
+ if (file_ref->type == ResourceFile::Type::kProtoXml) {
+ pb::XmlNode pb_xml_node;
+ if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to parse proto xml");
+ return false;
+ }
- if (!file_op.xml_to_flatten) {
- return false;
+ std::string error;
+ file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
+ if (file_op.xml_to_flatten == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to deserialize proto xml: " << error);
+ return false;
+ }
+ } else {
+ file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
+ context_->GetDiagnostics(), file->GetSource());
+ if (file_op.xml_to_flatten == nullptr) {
+ return false;
+ }
}
file_op.xml_to_flatten->file.config = config_value->config;
@@ -607,8 +650,9 @@
return false;
}
}
- error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values,
- false /*utf16*/, archive_writer);
+
+ error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
+ false /*utf16*/, options_.output_format, archive_writer);
}
} else {
error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
@@ -907,23 +951,29 @@
}
}
- bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
- BigBuffer buffer(1024);
- TableFlattener flattener(options_.table_flattener_options, &buffer);
- if (!flattener.Consume(context_, table)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
- return false;
+ bool FlattenTable(ResourceTable* table, OutputFormat format, IArchiveWriter* writer) {
+ switch (format) {
+ case OutputFormat::kApk: {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(options_.table_flattener_options, &buffer);
+ if (!flattener.Consume(context_, table)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
+ return false;
+ }
+
+ io::BigBufferInputStream input_stream(&buffer);
+ return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
+ ArchiveEntry::kAlign, writer);
+ } break;
+
+ case OutputFormat::kProto: {
+ pb::ResourceTable pb_table;
+ SerializeTableToPb(*table, &pb_table);
+ return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
+ ArchiveEntry::kCompress, writer);
+ } break;
}
-
- io::BigBufferInputStream input_stream(&buffer);
- return io::CopyInputStreamToArchive(context_, &input_stream, "resources.arsc",
- ArchiveEntry::kAlign, writer);
- }
-
- bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
- pb::ResourceTable pb_table;
- SerializeTableToPb(*table, &pb_table);
- return io::CopyProtoToArchive(context_, &pb_table, "resources.arsc.flat", 0, writer);
+ return false;
}
bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
@@ -1152,7 +1202,7 @@
}
std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
- io::IFile* file = collection->FindFile("resources.arsc.flat");
+ io::IFile* file = collection->FindFile(kProtoResourceTablePath);
if (!file) {
return {};
}
@@ -1201,11 +1251,7 @@
// Clear the package name, so as to make the resources look like they are coming from the
// local package.
pkg->name = "";
- if (override) {
- result = table_merger_->MergeOverlay(Source(input), table.get(), collection.get());
- } else {
- result = table_merger_->Merge(Source(input), table.get(), collection.get());
- }
+ result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
} else {
// This is the proper way to merge libraries, where the package name is
@@ -1241,49 +1287,34 @@
return false;
}
- bool result = false;
- if (override) {
- result = table_merger_->MergeOverlay(file->GetSource(), table.get());
- } else {
- result = table_merger_->Merge(file->GetSource(), table.get());
- }
- return result;
+ return table_merger_->Merge(file->GetSource(), table.get(), override);
}
- bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, bool override) {
+ bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
if (context_->IsVerbose()) {
- context_->GetDiagnostics()->Note(DiagMessage() << "merging '" << file_desc->name
- << "' from compiled file "
- << file->GetSource());
+ context_->GetDiagnostics()->Note(DiagMessage()
+ << "merging '" << compiled_file.name
+ << "' from compiled file " << compiled_file.source);
}
- bool result = false;
- if (override) {
- result = table_merger_->MergeFileOverlay(*file_desc, file);
- } else {
- result = table_merger_->MergeFile(*file_desc, file);
- }
-
- if (!result) {
+ if (!table_merger_->MergeFile(compiled_file, override, file)) {
return false;
}
// Add the exports of this file to the table.
- for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) {
- if (exported_symbol.name.package.empty()) {
- exported_symbol.name.package = context_->GetCompilationPackage();
+ for (const SourcedResourceName& exported_symbol : compiled_file.exported_symbols) {
+ ResourceName res_name = exported_symbol.name;
+ if (res_name.package.empty()) {
+ res_name.package = context_->GetCompilationPackage();
}
- ResourceNameRef res_name = exported_symbol.name;
-
- Maybe<ResourceName> mangled_name =
- context_->GetNameMangler()->MangleName(exported_symbol.name);
+ Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name);
if (mangled_name) {
res_name = mangled_name.value();
}
std::unique_ptr<Id> id = util::make_unique<Id>();
- id->SetSource(file_desc->source.WithLine(exported_symbol.line));
+ id->SetSource(compiled_file.source.WithLine(exported_symbol.line));
bool result = final_table_.AddResourceAllowMangled(
res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
context_->GetDiagnostics());
@@ -1294,15 +1325,11 @@
return true;
}
- /**
- * Takes a path to load as a ZIP file and merges the files within into the
- * master ResourceTable.
- * If override is true, conflicting resources are allowed to override each
- * other, in order of last seen.
- *
- * An io::IFileCollection is created from the ZIP file and added to the set of
- * io::IFileCollections that are open.
- */
+ // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
+ // If override is true, conflicting resources are allowed to override each other, in order of last
+ // seen.
+ // An io::IFileCollection is created from the ZIP file and added to the set of
+ // io::IFileCollections that are open.
bool MergeArchive(const std::string& input, bool override) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input);
@@ -1328,18 +1355,11 @@
return !error;
}
- /**
- * Takes a path to load and merge into the master ResourceTable. If override
- * is true,
- * conflicting resources are allowed to override each other, in order of last
- * seen.
- *
- * If the file path ends with .flata, .jar, .jack, or .zip the file is treated
- * as ZIP archive
- * and the files within are merged individually.
- *
- * Otherwise the files is processed on its own.
- */
+ // Takes a path to load and merge into the master ResourceTable. If override is true,
+ // conflicting resources are allowed to override each other, in order of last seen.
+ // If the file path ends with .flata, .jar, .jack, or .zip the file is treated
+ // as ZIP archive and the files within are merged individually.
+ // Otherwise the file is processed on its own.
bool MergePath(const std::string& path, bool override) {
if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") ||
util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) {
@@ -1352,70 +1372,15 @@
return MergeFile(file, override);
}
- /**
- * Takes a file to load and merge into the master ResourceTable. If override
- * is true,
- * conflicting resources are allowed to override each other, in order of last
- * seen.
- *
- * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and
- * merged into the
- * master ResourceTable. If the file ends with .flat, then it is treated like
- * a compiled file
- * and the header data is read and merged into the final ResourceTable.
- *
- * All other file types are ignored. This is because these files could be
- * coming from a zip,
- * where we could have other files like classes.dex.
- */
+ // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable.
+ // If override is true, conflicting resources are allowed to override each other, in order of last
+ // seen.
+ // All other file types are ignored. This is because these files could be coming from a zip,
+ // where we could have other files like classes.dex.
bool MergeFile(io::IFile* file, bool override) {
const Source& src = file->GetSource();
- if (util::EndsWith(src.path, ".arsc.flat")) {
- return MergeResourceTable(file, override);
- } else if (util::EndsWith(src.path, ".flat")) {
- // Try opening the file and looking for an Export header.
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (!data) {
- context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open");
- return false;
- }
-
- CompiledFileInputStream input_stream(data->data(), data->size());
- uint32_t num_files = 0;
- if (!input_stream.ReadLittleEndian32(&num_files)) {
- context_->GetDiagnostics()->Error(DiagMessage(src) << "failed read num files");
- return false;
- }
-
- for (uint32_t i = 0; i < num_files; i++) {
- pb::internal::CompiledFile compiled_file;
- if (!input_stream.ReadCompiledFile(&compiled_file)) {
- context_->GetDiagnostics()->Error(DiagMessage(src)
- << "failed to read compiled file header");
- return false;
- }
-
- uint64_t offset, len;
- if (!input_stream.ReadDataMetaData(&offset, &len)) {
- context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data");
- return false;
- }
-
- ResourceFile resource_file;
- std::string error;
- if (!DeserializeCompiledFileFromPb(compiled_file, &resource_file, &error)) {
- context_->GetDiagnostics()->Error(DiagMessage(src)
- << "failed to read compiled header: " << error);
- return false;
- }
-
- if (!MergeCompiledFile(file->CreateFileSegment(offset, len), &resource_file, override)) {
- return false;
- }
- }
- return true;
- } else if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) {
+ if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) {
// Since AAPT compiles these file types and appends .flat to them, seeing
// their raw extensions is a sign that they weren't compiled.
const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG";
@@ -1423,11 +1388,71 @@
<< " file passed as argument. Must be "
"compiled first into .flat file.");
return false;
+ } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) {
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file");
+ return true;
+ }
}
- // Ignore non .flat files. This could be classes.dex or something else that
- // happens
- // to be in an archive.
+ std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream();
+ if (input_stream == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file");
+ return false;
+ }
+
+ if (input_stream->HadError()) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed to open file: " << input_stream->GetError());
+ return false;
+ }
+
+ ContainerReaderEntry* entry;
+ ContainerReader reader(input_stream.get());
+ while ((entry = reader.Next()) != nullptr) {
+ if (entry->Type() == ContainerEntryType::kResTable) {
+ pb::ResourceTable pb_table;
+ if (!entry->GetResTable(&pb_table)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: "
+ << entry->GetError());
+ return false;
+ }
+
+ ResourceTable table;
+ std::string error;
+ if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed to deserialize resource table: " << error);
+ return false;
+ }
+
+ if (!table_merger_->Merge(src, &table, override)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table");
+ return false;
+ }
+ } else if (entry->Type() == ContainerEntryType::kResFile) {
+ pb::internal::CompiledFile pb_compiled_file;
+ off64_t offset;
+ size_t len;
+ if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: "
+ << entry->GetError());
+ return false;
+ }
+
+ ResourceFile resource_file;
+ std::string error;
+ if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) {
+ context_->GetDiagnostics()->Error(DiagMessage(src)
+ << "failed to read compiled header: " << error);
+ return false;
+ }
+
+ if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) {
+ return false;
+ }
+ }
+ }
return true;
}
@@ -1471,15 +1496,13 @@
return true;
}
- /**
- * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
- * the ResourceTable to the IArchiveWriter.
- */
+ // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
+ // to the IArchiveWriter.
bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
ResourceTable* table) {
const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib;
- bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values,
- true /*utf16*/, writer);
+ bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values,
+ true /*utf16*/, options_.output_format, writer);
if (!result) {
return false;
}
@@ -1494,6 +1517,7 @@
file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
file_flattener_options.update_proguard_spec =
static_cast<bool>(options_.generate_proguard_rules_path);
+ file_flattener_options.output_format = options_.output_format;
ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
@@ -1502,15 +1526,9 @@
return false;
}
- if (context_->GetPackageType() == PackageType::kStaticLib) {
- if (!FlattenTableToPb(table, writer)) {
- return false;
- }
- } else {
- if (!FlattenTable(table, writer)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc");
- return false;
- }
+ if (!FlattenTable(table, options_.output_format, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
+ return false;
}
return true;
}
@@ -1874,6 +1892,7 @@
bool verbose = false;
bool shared_lib = false;
bool static_lib = false;
+ bool proto_format = false;
Maybe<std::string> stable_id_file_path;
std::vector<std::string> split_args;
Flags flags =
@@ -1954,6 +1973,10 @@
.OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
&shared_lib)
.OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib)
+ .OptionalSwitch("--proto-format",
+ "Generates compiled resources in Protobuf format.\n"
+ "Suitable as input to the bundle tool for generating an App Bundle.",
+ &proto_format)
.OptionalSwitch("--no-static-lib-packages",
"Merge all library resources under the app's package.",
&options.no_static_lib_packages)
@@ -2040,21 +2063,25 @@
context.SetVerbose(verbose);
}
- if (shared_lib && static_lib) {
- context.GetDiagnostics()->Error(DiagMessage()
- << "only one of --shared-lib and --static-lib can be defined");
+ if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) {
+ context.GetDiagnostics()->Error(
+ DiagMessage()
+ << "only one of --shared-lib, --static-lib, or --proto_format can be defined");
return 1;
}
+ // The default build type.
+ context.SetPackageType(PackageType::kApp);
+ context.SetPackageId(kAppPackageId);
+
if (shared_lib) {
context.SetPackageType(PackageType::kSharedLib);
context.SetPackageId(0x00);
} else if (static_lib) {
context.SetPackageType(PackageType::kStaticLib);
- context.SetPackageId(kAppPackageId);
- } else {
- context.SetPackageType(PackageType::kApp);
- context.SetPackageId(kAppPackageId);
+ options.output_format = OutputFormat::kProto;
+ } else if (proto_format) {
+ options.output_format = OutputFormat::kProto;
}
if (package_id) {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 67ac67a..44e148e 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -33,7 +33,7 @@
#include "filter/AbiFilter.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
-#include "io/BigBufferInputStream.h"
+#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "optimize/MultiApkGenerator.h"
#include "optimize/ResourceDeduper.h"
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index d17858d..708bed8 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -72,7 +72,6 @@
}
*out_path = parts[0];
- std::vector<ConfigDescription> configs;
for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
ConfigDescription config;
if (!ConfigDescription::Parse(config_str, &config)) {
@@ -141,6 +140,16 @@
return decl;
}
+static std::string MakePackageSafeName(const std::string &name) {
+ std::string result(name);
+ for (char &c : result) {
+ if (c == '-') {
+ c = '_';
+ }
+ }
+ return result;
+}
+
std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
const SplitConstraints& constraints) {
const ResourceId kVersionCode(0x0101021b);
@@ -172,7 +181,11 @@
if (app_info.split_name) {
split_name << app_info.split_name.value() << ".";
}
- split_name << "config." << util::Joiner(constraints.configs, "_");
+ std::vector<std::string> sanitized_config_names;
+ for (const auto &config : constraints.configs) {
+ sanitized_config_names.push_back(MakePackageSafeName(config.toString().string()));
+ }
+ split_name << "config." << util::Joiner(sanitized_config_names, "_");
manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
new file mode 100644
index 0000000..9c33135
--- /dev/null
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "Util.h"
+
+#include "AppInfo.h"
+#include "split/TableSplitter.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(UtilTest, SplitNamesAreSanitized) {
+ AppInfo app_info{"com.pkg"};
+ SplitConstraints split_constraints{{test::ParseConfigOrDie("en-rUS-land")}};
+
+ const auto doc = GenerateSplitManifest(app_info, split_constraints);
+ const auto &root = doc->root;
+ EXPECT_EQ(root->name, "manifest");
+ // split names cannot contain hyphens
+ EXPECT_EQ(root->FindAttribute("", "split")->value, "config.en_rUS_land");
+ // but we should use resource qualifiers verbatim in 'targetConfig'.
+ EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "en-rUS-land");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index a79a577..b99240f 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -30,7 +30,7 @@
#include "ResourceUtils.h"
#include "io/File.h"
#include "io/FileSystem.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
new file mode 100644
index 0000000..739555c
--- /dev/null
+++ b/tools/aapt2/format/Container.cpp
@@ -0,0 +1,353 @@
+/*
+ * 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 "format/Container.h"
+
+#include "android-base/scopeguard.h"
+#include "android-base/stringprintf.h"
+
+using ::android::base::StringPrintf;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::CodedOutputStream;
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
+namespace aapt {
+
+constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
+constexpr const static uint32_t kContainerFormatVersion = 1u;
+
+ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
+ : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
+ CodedOutputStream coded_out(out_);
+
+ // Write the magic.
+ coded_out.WriteLittleEndian32(kContainerFormatMagic);
+
+ // Write the version.
+ coded_out.WriteLittleEndian32(kContainerFormatVersion);
+
+ // Write the total number of entries.
+ coded_out.WriteLittleEndian32(static_cast<uint32_t>(total_entry_count_));
+
+ if (coded_out.HadError()) {
+ error_ = "failed writing container format header";
+ }
+}
+
+inline static void WritePadding(int padding, CodedOutputStream* out) {
+ if (padding < 4) {
+ const uint32_t zero = 0u;
+ out->WriteRaw(&zero, padding);
+ }
+}
+
+bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
+ if (current_entry_count_ >= total_entry_count_) {
+ error_ = "too many entries being serialized";
+ return false;
+ }
+ current_entry_count_++;
+
+ CodedOutputStream coded_out(out_);
+
+ // Write the type.
+ coded_out.WriteLittleEndian32(kResTable);
+
+ // Write the aligned size.
+ const ::google::protobuf::uint64 size = table.ByteSize();
+ const int padding = 4 - (size % 4);
+ coded_out.WriteLittleEndian64(size);
+
+ // Write the table.
+ table.SerializeWithCachedSizes(&coded_out);
+
+ // Write the padding.
+ WritePadding(padding, &coded_out);
+
+ if (coded_out.HadError()) {
+ error_ = "failed writing to output";
+ return false;
+ }
+ return true;
+}
+
+bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
+ io::KnownSizeInputStream* in) {
+ if (current_entry_count_ >= total_entry_count_) {
+ error_ = "too many entries being serialized";
+ return false;
+ }
+ current_entry_count_++;
+
+ constexpr const static int kResFileEntryHeaderSize = 12;
+
+ CodedOutputStream coded_out(out_);
+
+ // Write the type.
+ coded_out.WriteLittleEndian32(kResFile);
+
+ // Write the aligned size.
+ const ::google::protobuf::uint32 header_size = file.ByteSize();
+ const int header_padding = 4 - (header_size % 4);
+ const ::google::protobuf::uint64 data_size = in->TotalSize();
+ const int data_padding = 4 - (data_size % 4);
+ coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
+ data_padding);
+
+ // Write the res file header size.
+ coded_out.WriteLittleEndian32(header_size);
+
+ // Write the data payload size.
+ coded_out.WriteLittleEndian64(data_size);
+
+ // Write the header.
+ file.SerializeToCodedStream(&coded_out);
+
+ WritePadding(header_padding, &coded_out);
+
+ // Write the data payload. We need to call Trim() since we are going to write to the underlying
+ // ZeroCopyOutputStream.
+ coded_out.Trim();
+
+ // Check at this point if there were any errors.
+ if (coded_out.HadError()) {
+ error_ = "failed writing to output";
+ return false;
+ }
+
+ if (!io::Copy(out_, in)) {
+ if (in->HadError()) {
+ std::ostringstream error;
+ error << "failed reading from input: " << in->GetError();
+ error_ = error.str();
+ } else {
+ error_ = "failed writing to output";
+ }
+ return false;
+ }
+ WritePadding(data_padding, &coded_out);
+
+ if (coded_out.HadError()) {
+ error_ = "failed writing to output";
+ return false;
+ }
+ return true;
+}
+
+bool ContainerWriter::HadError() const {
+ return !error_.empty();
+}
+
+std::string ContainerWriter::GetError() const {
+ return error_;
+}
+
+static bool AlignRead(CodedInputStream* in) {
+ const int padding = 4 - (in->CurrentPosition() % 4);
+ if (padding < 4) {
+ return in->Skip(padding);
+ }
+ return true;
+}
+
+ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
+}
+
+ContainerEntryType ContainerReaderEntry::Type() const {
+ return type_;
+}
+
+bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) {
+ CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile";
+ if (length_ > std::numeric_limits<int>::max()) {
+ reader_->error_ = StringPrintf("entry length %zu is too large", length_);
+ return false;
+ }
+
+ CodedInputStream& coded_in = reader_->coded_in_;
+
+ const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(length_));
+ auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+ if (!out_table->ParseFromCodedStream(&coded_in)) {
+ reader_->error_ = "failed to parse ResourceTable";
+ return false;
+ }
+ return true;
+}
+
+bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file,
+ off64_t* out_offset, size_t* out_len) {
+ CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable";
+
+ CodedInputStream& coded_in = reader_->coded_in_;
+
+ // Read the ResFile header.
+ ::google::protobuf::uint32 header_length;
+ if (!coded_in.ReadLittleEndian32(&header_length)) {
+ std::ostringstream error;
+ error << "failed to read header length from input: " << reader_->in_->GetError();
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ ::google::protobuf::uint64 data_length;
+ if (!coded_in.ReadLittleEndian64(&data_length)) {
+ std::ostringstream error;
+ error << "failed to read data length from input: " << reader_->in_->GetError();
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ if (header_length > std::numeric_limits<int>::max()) {
+ std::ostringstream error;
+ error << "header length " << header_length << " is too large";
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ if (data_length > std::numeric_limits<size_t>::max()) {
+ std::ostringstream error;
+ error << "data length " << data_length << " is too large";
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ {
+ const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(header_length));
+ auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+ if (!out_file->ParseFromCodedStream(&coded_in)) {
+ reader_->error_ = "failed to parse CompiledFile header";
+ return false;
+ }
+ }
+
+ AlignRead(&coded_in);
+
+ *out_offset = coded_in.CurrentPosition();
+ *out_len = data_length;
+
+ coded_in.Skip(static_cast<int>(data_length));
+ AlignRead(&coded_in);
+ return true;
+}
+
+bool ContainerReaderEntry::HadError() const {
+ return reader_->HadError();
+}
+
+std::string ContainerReaderEntry::GetError() const {
+ return reader_->GetError();
+}
+
+ContainerReader::ContainerReader(io::InputStream* in)
+ : in_(in),
+ adaptor_(in),
+ coded_in_(&adaptor_),
+ total_entry_count_(0u),
+ current_entry_count_(0u),
+ entry_(this) {
+ ::google::protobuf::uint32 magic;
+ if (!coded_in_.ReadLittleEndian32(&magic)) {
+ std::ostringstream error;
+ error << "failed to read magic from input: " << in_->GetError();
+ error_ = error.str();
+ return;
+ }
+
+ if (magic != kContainerFormatMagic) {
+ error_ = "magic value doesn't match AAPT";
+ return;
+ }
+
+ ::google::protobuf::uint32 version;
+ if (!coded_in_.ReadLittleEndian32(&version)) {
+ std::ostringstream error;
+ error << "failed to read version from input: " << in_->GetError();
+ error_ = error.str();
+ return;
+ }
+
+ if (version != kContainerFormatVersion) {
+ error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version,
+ kContainerFormatVersion);
+ return;
+ }
+
+ ::google::protobuf::uint32 total_entry_count;
+ if (!coded_in_.ReadLittleEndian32(&total_entry_count)) {
+ std::ostringstream error;
+ error << "failed to read entry count from input: " << in_->GetError();
+ error_ = error.str();
+ return;
+ }
+
+ total_entry_count_ = total_entry_count;
+}
+
+ContainerReaderEntry* ContainerReader::Next() {
+ if (current_entry_count_ >= total_entry_count_) {
+ return nullptr;
+ }
+ current_entry_count_++;
+
+ // Ensure the next read is aligned.
+ AlignRead(&coded_in_);
+
+ ::google::protobuf::uint32 entry_type;
+ if (!coded_in_.ReadLittleEndian32(&entry_type)) {
+ std::ostringstream error;
+ error << "failed reading entry type from input: " << in_->GetError();
+ error_ = error.str();
+ return nullptr;
+ }
+
+ ::google::protobuf::uint64 entry_length;
+ if (!coded_in_.ReadLittleEndian64(&entry_length)) {
+ std::ostringstream error;
+ error << "failed reading entry length from input: " << in_->GetError();
+ error_ = error.str();
+ return nullptr;
+ }
+
+ if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) {
+ entry_.type_ = static_cast<ContainerEntryType>(entry_type);
+ } else {
+ error_ = StringPrintf("entry type 0x%08x is invalid", entry_type);
+ return nullptr;
+ }
+
+ if (entry_length > std::numeric_limits<size_t>::max()) {
+ std::ostringstream error;
+ error << "entry length " << entry_length << " is too large";
+ error_ = error.str();
+ return nullptr;
+ }
+
+ entry_.length_ = entry_length;
+ return &entry_;
+}
+
+bool ContainerReader::HadError() const {
+ return !error_.empty();
+}
+
+std::string ContainerReader::GetError() const {
+ return error_;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h
new file mode 100644
index 0000000..aa5c82c
--- /dev/null
+++ b/tools/aapt2/format/Container.h
@@ -0,0 +1,106 @@
+/*
+ * 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 AAPT_FORMAT_CONTAINER_H
+#define AAPT_FORMAT_CONTAINER_H
+
+#include <inttypes.h>
+
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream.h"
+
+#include "Resources.pb.h"
+#include "ResourcesInternal.pb.h"
+#include "io/Io.h"
+#include "io/Util.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+enum ContainerEntryType : uint8_t {
+ kResTable = 0x00u,
+ kResFile = 0x01u,
+};
+
+class ContainerWriter {
+ public:
+ explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count);
+
+ bool AddResTableEntry(const pb::ResourceTable& table);
+ bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in);
+ bool HadError() const;
+ std::string GetError() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContainerWriter);
+
+ ::google::protobuf::io::ZeroCopyOutputStream* out_;
+ size_t total_entry_count_;
+ size_t current_entry_count_;
+ std::string error_;
+};
+
+class ContainerReader;
+
+class ContainerReaderEntry {
+ public:
+ ContainerEntryType Type() const;
+
+ bool GetResTable(pb::ResourceTable* out_table);
+ bool GetResFileOffsets(pb::internal::CompiledFile* out_file, off64_t* out_offset,
+ size_t* out_len);
+
+ bool HadError() const;
+ std::string GetError() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContainerReaderEntry);
+
+ friend class ContainerReader;
+
+ explicit ContainerReaderEntry(ContainerReader* reader);
+
+ ContainerReader* reader_;
+ ContainerEntryType type_ = ContainerEntryType::kResTable;
+ size_t length_ = 0u;
+};
+
+class ContainerReader {
+ public:
+ explicit ContainerReader(io::InputStream* in);
+
+ ContainerReaderEntry* Next();
+
+ bool HadError() const;
+ std::string GetError() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContainerReader);
+
+ friend class ContainerReaderEntry;
+
+ io::InputStream* in_;
+ io::ZeroCopyInputAdaptor adaptor_;
+ ::google::protobuf::io::CodedInputStream coded_in_;
+ size_t total_entry_count_;
+ size_t current_entry_count_;
+ ContainerReaderEntry entry_;
+ std::string error_;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FORMAT_CONTAINER_H */
diff --git a/tools/aapt2/format/Container_test.cpp b/tools/aapt2/format/Container_test.cpp
new file mode 100644
index 0000000..dc81a3a
--- /dev/null
+++ b/tools/aapt2/format/Container_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "format/Container.h"
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+#include "io/StringStream.h"
+#include "test/Test.h"
+
+using ::google::protobuf::io::StringOutputStream;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace aapt {
+
+TEST(ContainerTest, SerializeCompiledFile) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ const std::string expected_data = "123";
+
+ std::string output_str;
+ {
+ StringOutputStream out_stream(&output_str);
+ ContainerWriter writer(&out_stream, 2u);
+ ASSERT_FALSE(writer.HadError());
+
+ pb::internal::CompiledFile pb_compiled_file;
+ pb_compiled_file.set_resource_name("android:layout/main.xml");
+ pb_compiled_file.set_type(pb::FileReference::PROTO_XML);
+ pb_compiled_file.set_source_path("res/layout/main.xml");
+ io::StringInputStream data(expected_data);
+ ASSERT_TRUE(writer.AddResFileEntry(pb_compiled_file, &data));
+
+ pb::ResourceTable pb_table;
+ pb::Package* pb_pkg = pb_table.add_package();
+ pb_pkg->set_package_name("android");
+ pb_pkg->mutable_package_id()->set_id(0x01u);
+ ASSERT_TRUE(writer.AddResTableEntry(pb_table));
+
+ ASSERT_FALSE(writer.HadError());
+ }
+
+ io::StringInputStream input(output_str);
+ ContainerReader reader(&input);
+ ASSERT_FALSE(reader.HadError());
+
+ ContainerReaderEntry* entry = reader.Next();
+ ASSERT_THAT(entry, NotNull());
+ ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResFile));
+
+ pb::internal::CompiledFile pb_new_file;
+ off64_t offset;
+ size_t len;
+ ASSERT_TRUE(entry->GetResFileOffsets(&pb_new_file, &offset, &len)) << entry->GetError();
+ EXPECT_THAT(offset & 0x03, Eq(0u));
+ EXPECT_THAT(output_str.substr(static_cast<size_t>(offset), len), StrEq(expected_data));
+
+ entry = reader.Next();
+ ASSERT_THAT(entry, NotNull());
+ ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResTable));
+
+ pb::ResourceTable pb_new_table;
+ ASSERT_TRUE(entry->GetResTable(&pb_new_table));
+ ASSERT_THAT(pb_new_table.package_size(), Eq(1));
+ EXPECT_THAT(pb_new_table.package(0).package_name(), StrEq("android"));
+ EXPECT_THAT(pb_new_table.package(0).package_id().id(), Eq(0x01u));
+
+ EXPECT_THAT(reader.Next(), IsNull());
+ EXPECT_FALSE(reader.HadError());
+ EXPECT_THAT(reader.GetError(), IsEmpty());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 2456c3d..345cc95 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -56,9 +56,9 @@
return false;
}
-class XmlFlattenerVisitor : public xml::Visitor {
+class XmlFlattenerVisitor : public xml::ConstVisitor {
public:
- using xml::Visitor::Visit;
+ using xml::ConstVisitor::Visit;
StringPool pool;
std::map<uint8_t, StringPool> package_pools;
@@ -74,7 +74,7 @@
: buffer_(buffer), options_(options) {
}
- void Visit(xml::Text* node) override {
+ void Visit(const xml::Text* node) override {
if (util::TrimWhitespace(node->text).empty()) {
// Skip whitespace only text nodes.
return;
@@ -95,7 +95,7 @@
writer.Finish();
}
- void Visit(xml::Element* node) override {
+ void Visit(const xml::Element* node) override {
for (const xml::NamespaceDecl& decl : node->namespace_decls) {
// Skip dedicated tools namespace.
if (decl.uri != xml::kSchemaTools) {
@@ -125,7 +125,7 @@
start_writer.Finish();
}
- xml::Visitor::Visit(node);
+ xml::ConstVisitor::Visit(node);
{
ChunkWriter end_writer(buffer_);
@@ -182,12 +182,13 @@
writer.Finish();
}
- void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, ChunkWriter* writer) {
+ void WriteAttributes(const xml::Element* node, ResXMLTree_attrExt* flat_elem,
+ ChunkWriter* writer) {
filtered_attrs_.clear();
filtered_attrs_.reserve(node->attributes.size());
// Filter the attributes.
- for (xml::Attribute& attr : node->attributes) {
+ for (const xml::Attribute& attr : node->attributes) {
if (attr.namespace_uri != xml::kSchemaTools) {
filtered_attrs_.push_back(&attr);
}
@@ -282,12 +283,12 @@
XmlFlattenerOptions options_;
// Scratch vector to filter attributes. We avoid allocations making this a member.
- std::vector<xml::Attribute*> filtered_attrs_;
+ std::vector<const xml::Attribute*> filtered_attrs_;
};
} // namespace
-bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
+bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) {
BigBuffer node_buffer(1024);
XmlFlattenerVisitor visitor(&node_buffer, options_);
node->Accept(&visitor);
@@ -341,7 +342,7 @@
return true;
}
-bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
+bool XmlFlattener::Consume(IAaptContext* context, const xml::XmlResource* resource) {
if (!resource->root) {
return false;
}
diff --git a/tools/aapt2/format/binary/XmlFlattener.h b/tools/aapt2/format/binary/XmlFlattener.h
index 8db2281..1f9e777 100644
--- a/tools/aapt2/format/binary/XmlFlattener.h
+++ b/tools/aapt2/format/binary/XmlFlattener.h
@@ -34,18 +34,18 @@
bool use_utf16 = false;
};
-class XmlFlattener : public IXmlResourceConsumer {
+class XmlFlattener {
public:
XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
: buffer_(buffer), options_(options) {
}
- bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
+ bool Consume(IAaptContext* context, const xml::XmlResource* resource);
private:
DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
- bool Flatten(IAaptContext* context, xml::Node* node);
+ bool Flatten(IAaptContext* context, const xml::Node* node);
BigBuffer* buffer_;
XmlFlattenerOptions options_;
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index c14f09a..86bd865 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -26,7 +26,6 @@
#include "ValueVisitor.h"
using ::android::ResStringPool;
-using ::google::protobuf::io::CodedInputStream;
namespace aapt {
@@ -391,8 +390,15 @@
}
ResourceTableType* type = pkg->FindOrCreateType(*res_type);
+ if (pb_type.has_type_id()) {
+ type->id = static_cast<uint8_t>(pb_type.type_id().id());
+ }
+
for (const pb::Entry& pb_entry : pb_type.entry()) {
ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name());
+ if (pb_entry.has_entry_id()) {
+ entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
+ }
// Deserialize the symbol status (public/private with source and comments).
if (pb_entry.has_symbol_status()) {
@@ -406,21 +412,11 @@
const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
entry->symbol_status.state = visibility;
-
if (visibility == SymbolState::kPublic) {
- // This is a public symbol, we must encode the ID now if there is one.
- if (pb_entry.has_entry_id()) {
- entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
- }
-
- if (type->symbol_status.state != SymbolState::kPublic) {
- // If the type has not been made public, do so now.
- type->symbol_status.state = SymbolState::kPublic;
- if (pb_type.has_type_id()) {
- type->id = static_cast<uint8_t>(pb_type.type_id().id());
- }
- }
+ // Propagate the public visibility up to the Type.
+ type->symbol_status.state = SymbolState::kPublic;
} else if (visibility == SymbolState::kPrivate) {
+ // Only propagate if no previous state was assigned.
if (type->symbol_status.state == SymbolState::kUndefined) {
type->symbol_status.state = SymbolState::kPrivate;
}
@@ -485,6 +481,19 @@
return true;
}
+static ResourceFile::Type DeserializeFileReferenceTypeFromPb(const pb::FileReference::Type& type) {
+ switch (type) {
+ case pb::FileReference::BINARY_XML:
+ return ResourceFile::Type::kBinaryXml;
+ case pb::FileReference::PROTO_XML:
+ return ResourceFile::Type::kProtoXml;
+ case pb::FileReference::PNG:
+ return ResourceFile::Type::kPng;
+ default:
+ return ResourceFile::Type::kUnknown;
+ }
+}
+
bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
ResourceFile* out_file, std::string* out_error) {
ResourceNameRef name_ref;
@@ -497,6 +506,7 @@
out_file->name = name_ref.ToResourceName();
out_file->source.path = pb_file.source_path();
+ out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
std::string config_error;
if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) {
@@ -759,8 +769,12 @@
} break;
case pb::Item::kFile: {
- return util::make_unique<FileReference>(value_pool->MakeRef(
- pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
+ const pb::FileReference& pb_file = pb_item.file();
+ std::unique_ptr<FileReference> file_ref =
+ util::make_unique<FileReference>(value_pool->MakeRef(
+ pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
+ file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ return std::move(file_ref);
} break;
default:
@@ -847,72 +861,4 @@
return true;
}
-CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size)
- : in_(static_cast<const uint8_t*>(data), size) {
-}
-
-void CompiledFileInputStream::EnsureAlignedRead() {
- const int overflow = in_.CurrentPosition() % 4;
- if (overflow > 0) {
- // Reads are always 4 byte aligned.
- in_.Skip(4 - overflow);
- }
-}
-
-bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) {
- EnsureAlignedRead();
- return in_.ReadLittleEndian32(out_val);
-}
-
-bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) {
- EnsureAlignedRead();
-
- google::protobuf::uint64 pb_size = 0u;
- if (!in_.ReadLittleEndian64(&pb_size)) {
- return false;
- }
-
- CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size));
-
- // Check that we haven't tried to read past the end.
- if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) {
- in_.PopLimit(l);
- in_.PushLimit(0);
- return false;
- }
-
- if (!out_val->ParsePartialFromCodedStream(&in_)) {
- in_.PopLimit(l);
- in_.PushLimit(0);
- return false;
- }
-
- in_.PopLimit(l);
- return true;
-}
-
-bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) {
- EnsureAlignedRead();
-
- google::protobuf::uint64 pb_size = 0u;
- if (!in_.ReadLittleEndian64(&pb_size)) {
- return false;
- }
-
- // Check that we aren't trying to read past the end.
- if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) {
- in_.PushLimit(0);
- return false;
- }
-
- uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition());
- if (!in_.Skip(pb_size)) {
- return false;
- }
-
- *out_offset = offset;
- *out_len = pb_size;
- return true;
-}
-
} // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
index c8a7199..7dc54f2 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.h
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -19,7 +19,6 @@
#include "android-base/macros.h"
#include "androidfw/ResourceTypes.h"
-#include "google/protobuf/io/coded_stream.h"
#include "ConfigDescription.h"
#include "Configuration.pb.h"
@@ -57,22 +56,6 @@
bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
ResourceFile* out_file, std::string* out_error);
-class CompiledFileInputStream {
- public:
- explicit CompiledFileInputStream(const void* data, size_t size);
-
- bool ReadLittleEndian32(uint32_t* outVal);
- bool ReadCompiledFile(pb::internal::CompiledFile* outVal);
- bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
-
- private:
- DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
-
- void EnsureAlignedRead();
-
- ::google::protobuf::io::CodedInputStream in_;
-};
-
} // namespace aapt
#endif /* AAPT_FORMAT_PROTO_PROTODESERIALIZE_H */
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index c0d3614..1d184fe 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -16,14 +16,9 @@
#include "format/proto/ProtoSerialize.h"
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
#include "ValueVisitor.h"
#include "util/BigBuffer.h"
-using ::google::protobuf::io::CodedOutputStream;
-using ::google::protobuf::io::ZeroCopyOutputStream;
-
namespace aapt {
void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) {
@@ -366,6 +361,19 @@
return pb::Plural_Arity_OTHER;
}
+static pb::FileReference::Type SerializeFileReferenceTypeToPb(const ResourceFile::Type& type) {
+ switch (type) {
+ case ResourceFile::Type::kBinaryXml:
+ return pb::FileReference::BINARY_XML;
+ case ResourceFile::Type::kProtoXml:
+ return pb::FileReference::PROTO_XML;
+ case ResourceFile::Type::kPng:
+ return pb::FileReference::PNG;
+ default:
+ return pb::FileReference::UNKNOWN;
+ }
+}
+
namespace {
class ValueSerializer : public ConstValueVisitor {
@@ -400,7 +408,9 @@
}
void Visit(const FileReference* file) override {
- out_value_->mutable_item()->mutable_file()->set_path(*file->path);
+ pb::FileReference* pb_file = out_value_->mutable_item()->mutable_file();
+ pb_file->set_path(*file->path);
+ pb_file->set_type(SerializeFileReferenceTypeToPb(file->type));
}
void Visit(const Id* /*id*/) override {
@@ -515,6 +525,7 @@
void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
out_file->set_resource_name(file.name.ToString());
out_file->set_source_path(file.source.path);
+ out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
SerializeConfig(file.config, out_file->mutable_config());
for (const SourcedResourceName& exported : file.exported_symbols) {
@@ -579,44 +590,4 @@
SerializeXmlToPb(*resource.root, out_node);
}
-CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) {
-}
-
-void CompiledFileOutputStream::EnsureAlignedWrite() {
- const int overflow = out_.ByteCount() % 4;
- if (overflow > 0) {
- uint32_t zero = 0u;
- out_.WriteRaw(&zero, 4 - overflow);
- }
-}
-
-void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {
- EnsureAlignedWrite();
- out_.WriteLittleEndian32(val);
-}
-
-void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile& compiled_file) {
- EnsureAlignedWrite();
- out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file.ByteSize()));
- compiled_file.SerializeWithCachedSizes(&out_);
-}
-
-void CompiledFileOutputStream::WriteData(const BigBuffer& buffer) {
- EnsureAlignedWrite();
- out_.WriteLittleEndian64(static_cast<uint64_t>(buffer.size()));
- for (const BigBuffer::Block& block : buffer) {
- out_.WriteRaw(block.buffer.get(), block.size);
- }
-}
-
-void CompiledFileOutputStream::WriteData(const void* data, size_t len) {
- EnsureAlignedWrite();
- out_.WriteLittleEndian64(static_cast<uint64_t>(len));
- out_.WriteRaw(data, len);
-}
-
-bool CompiledFileOutputStream::HadError() {
- return out_.HadError();
-}
-
} // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
index 1694b16..95dd413 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.h
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -18,7 +18,6 @@
#define AAPT_FORMAT_PROTO_PROTOSERIALIZE_H
#include "android-base/macros.h"
-#include "google/protobuf/io/coded_stream.h"
#include "ConfigDescription.h"
#include "Configuration.pb.h"
@@ -29,14 +28,6 @@
#include "StringPool.h"
#include "xml/XmlDom.h"
-namespace google {
-namespace protobuf {
-namespace io {
-class ZeroCopyOutputStream;
-} // namespace io
-} // namespace protobuf
-} // namespace google
-
namespace aapt {
// Serializes a Value to its protobuf representation. An optional StringPool will hold the
@@ -66,24 +57,6 @@
// Serializes a ResourceFile into its protobuf representation.
void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file);
-class CompiledFileOutputStream {
- public:
- explicit CompiledFileOutputStream(::google::protobuf::io::ZeroCopyOutputStream* out);
-
- void WriteLittleEndian32(uint32_t value);
- void WriteCompiledFile(const pb::internal::CompiledFile& compiledFile);
- void WriteData(const BigBuffer& buffer);
- void WriteData(const void* data, size_t len);
- bool HadError();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
-
- void EnsureAlignedWrite();
-
- ::google::protobuf::io::CodedOutputStream out_;
-};
-
} // namespace aapt
#endif /* AAPT_FORMAT_PROTO_PROTOSERIALIZE_H */
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 2154d5a..8efac8a 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -16,14 +16,11 @@
#include "format/proto/ProtoSerialize.h"
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
#include "ResourceUtils.h"
#include "format/proto/ProtoDeserialize.h"
#include "test/Test.h"
using ::android::StringPiece;
-using ::google::protobuf::io::StringOutputStream;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::NotNull;
@@ -137,113 +134,6 @@
EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
}
-TEST(ProtoSerializeTest, SerializeFileHeader) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
- ResourceFile f;
- f.config = test::ParseConfigOrDie("hdpi-v9");
- f.name = test::ParseNameOrDie("com.app.a:layout/main");
- f.source.path = "res/layout-hdpi-v9/main.xml";
- f.exported_symbols.push_back(SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u});
-
- const std::string expected_data1 = "123";
- const std::string expected_data2 = "1234";
-
- std::string output_str;
- {
- pb::internal::CompiledFile pb_f1, pb_f2;
- SerializeCompiledFileToPb(f, &pb_f1);
-
- f.name.entry = "__" + f.name.entry + "$0";
- SerializeCompiledFileToPb(f, &pb_f2);
-
- StringOutputStream out_stream(&output_str);
- CompiledFileOutputStream out_file_stream(&out_stream);
- out_file_stream.WriteLittleEndian32(2);
- out_file_stream.WriteCompiledFile(pb_f1);
- out_file_stream.WriteData(expected_data1.data(), expected_data1.size());
- out_file_stream.WriteCompiledFile(pb_f2);
- out_file_stream.WriteData(expected_data2.data(), expected_data2.size());
- ASSERT_FALSE(out_file_stream.HadError());
- }
-
- CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
- uint32_t num_files = 0;
- ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
- ASSERT_EQ(2u, num_files);
-
- // Read the first compiled file.
-
- pb::internal::CompiledFile new_pb_f1;
- ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f1));
-
- ResourceFile new_f1;
- std::string error;
- ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f1, &new_f1, &error));
- EXPECT_THAT(error, IsEmpty());
-
- uint64_t offset, len;
- ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
-
- std::string actual_data(output_str.data() + offset, len);
- EXPECT_EQ(expected_data1, actual_data);
-
- // Expect the data to be aligned.
- EXPECT_EQ(0u, offset & 0x03);
-
- ASSERT_EQ(1u, new_f1.exported_symbols.size());
- EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), new_f1.exported_symbols[0].name);
-
- // Read the second compiled file.
-
- pb::internal::CompiledFile new_pb_f2;
- ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f2));
-
- ResourceFile new_f2;
- ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f2, &new_f2, &error));
- EXPECT_THAT(error, IsEmpty());
-
- ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
-
- actual_data = std::string(output_str.data() + offset, len);
- EXPECT_EQ(expected_data2, actual_data);
-
- // Expect the data to be aligned.
- EXPECT_EQ(0u, offset & 0x03);
-}
-
-TEST(ProtoSerializeTest, DeserializeCorruptHeaderSafely) {
- ResourceFile f;
- pb::internal::CompiledFile pb_file;
- SerializeCompiledFileToPb(f, &pb_file);
-
- const std::string expected_data = "1234";
-
- std::string output_str;
- {
- StringOutputStream out_stream(&output_str);
- CompiledFileOutputStream out_file_stream(&out_stream);
- out_file_stream.WriteLittleEndian32(1);
- out_file_stream.WriteCompiledFile(pb_file);
- out_file_stream.WriteData(expected_data.data(), expected_data.size());
- ASSERT_FALSE(out_file_stream.HadError());
- }
-
- output_str[4] = 0xff;
-
- CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
-
- uint32_t num_files = 0;
- EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
- EXPECT_EQ(1u, num_files);
-
- pb::internal::CompiledFile new_pb_file;
- EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file));
-
- uint64_t offset, len;
- EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len));
-}
-
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
xml::Element element;
element.line_number = 22;
diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md
new file mode 100644
index 0000000..bb31a00
--- /dev/null
+++ b/tools/aapt2/formats.md
@@ -0,0 +1,44 @@
+# AAPT2 On-Disk Formats
+- AAPT2 Container Format (extension `.apc`)
+- AAPT2 Static Library Format (extension `.sapk`)
+
+## AAPT2 Container Format (extension `.apc`)
+The APC format (AAPT2 Container Format) is generated by AAPT2 during the compile phase and
+consumed by the AAPT2 link phase. It is a simple container format for storing compiled PNGs,
+binary and protobuf XML, and intermediate protobuf resource tables. It also stores all associated
+meta-data from the compile phase.
+
+### Format
+The file starts with a simple header. All multi-byte fields are little-endian.
+
+| Size (in bytes) | Field | Description |
+|:----------------|:--------------|:-----------------------------------------------------|
+| `4` | `magic` | The magic bytes must equal `'AAPT'` or `0x54504141`. |
+| `4` | `version` | The version of the container format. |
+| `4` | `entry_count` | The number of entries in this container. |
+
+This is followed by `entry_count` of the following data structure. It must be aligned on a 32-bit
+boundary, so if a previous entry ends unaligned, padding must be inserted.
+
+| Size (in bytes) | Field | Description |
+|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
+| `4` | `entry_type` | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. |
+| `8` | `entry_length` | The length of the data that follows. |
+| `entry_length` | `data` | The payload. The contents of this varies based on the `entry_type`. |
+
+If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized
+[aapt.pb.ResourceTable](Resources.proto).
+
+If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following:
+
+
+| Size (in bytes) | Field | Description |
+|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
+| `4` | `header_size` | The size of the `header` field. |
+| `8` | `data_size` | The size of the `data` field. |
+| `header_size` | `header` | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto). |
+| `x` | `padding` | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. |
+| `data_size` | `data` | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+
+## AAPT2 Static Library Format (extension `.sapk`)
+
diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h
deleted file mode 100644
index 95113bc..0000000
--- a/tools/aapt2/io/BigBufferOutputStream.h
+++ /dev/null
@@ -1,48 +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.
- */
-
-#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
-#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
-
-#include "io/Io.h"
-#include "util/BigBuffer.h"
-
-namespace aapt {
-namespace io {
-
-class BigBufferOutputStream : public OutputStream {
- public:
- inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
- virtual ~BigBufferOutputStream() = default;
-
- bool Next(void** data, size_t* size) override;
-
- void BackUp(size_t count) override;
-
- size_t ByteCount() const override;
-
- bool HadError() const override;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
-
- BigBuffer* buffer_;
-};
-
-} // namespace io
-} // namespace aapt
-
-#endif // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStream.cpp
similarity index 74%
rename from tools/aapt2/io/BigBufferStreams.cpp
rename to tools/aapt2/io/BigBufferStream.cpp
index eb99033..9704caa 100644
--- a/tools/aapt2/io/BigBufferStreams.cpp
+++ b/tools/aapt2/io/BigBufferStream.cpp
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#include "io/BigBufferInputStream.h"
-#include "io/BigBufferOutputStream.h"
+#include "io/BigBufferStream.h"
namespace aapt {
namespace io {
@@ -54,7 +53,9 @@
}
}
-bool BigBufferInputStream::CanRewind() const { return true; }
+bool BigBufferInputStream::CanRewind() const {
+ return true;
+}
bool BigBufferInputStream::Rewind() {
iter_ = buffer_->begin();
@@ -63,9 +64,17 @@
return true;
}
-size_t BigBufferInputStream::ByteCount() const { return bytes_read_; }
+size_t BigBufferInputStream::ByteCount() const {
+ return bytes_read_;
+}
-bool BigBufferInputStream::HadError() const { return false; }
+bool BigBufferInputStream::HadError() const {
+ return false;
+}
+
+size_t BigBufferInputStream::TotalSize() const {
+ return buffer_->size();
+}
//
// BigBufferOutputStream
@@ -76,11 +85,17 @@
return true;
}
-void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); }
+void BigBufferOutputStream::BackUp(size_t count) {
+ buffer_->BackUp(count);
+}
-size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); }
+size_t BigBufferOutputStream::ByteCount() const {
+ return buffer_->size();
+}
-bool BigBufferOutputStream::HadError() const { return false; }
+bool BigBufferOutputStream::HadError() const {
+ return false;
+}
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferStream.h
similarity index 64%
rename from tools/aapt2/io/BigBufferInputStream.h
rename to tools/aapt2/io/BigBufferStream.h
index 92612c7..8b5c8b8 100644
--- a/tools/aapt2/io/BigBufferInputStream.h
+++ b/tools/aapt2/io/BigBufferStream.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H
-#define AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#ifndef AAPT_IO_BIGBUFFERSTREAM_H
+#define AAPT_IO_BIGBUFFERSTREAM_H
#include "io/Io.h"
#include "util/BigBuffer.h"
@@ -23,10 +23,11 @@
namespace aapt {
namespace io {
-class BigBufferInputStream : public InputStream {
+class BigBufferInputStream : public KnownSizeInputStream {
public:
inline explicit BigBufferInputStream(const BigBuffer* buffer)
- : buffer_(buffer), iter_(buffer->begin()) {}
+ : buffer_(buffer), iter_(buffer->begin()) {
+ }
virtual ~BigBufferInputStream() = default;
bool Next(const void** data, size_t* size) override;
@@ -41,6 +42,8 @@
bool HadError() const override;
+ size_t TotalSize() const override;
+
private:
DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
@@ -50,7 +53,27 @@
size_t bytes_read_ = 0;
};
+class BigBufferOutputStream : public OutputStream {
+ public:
+ inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+ }
+ virtual ~BigBufferOutputStream() = default;
+
+ bool Next(void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+ BigBuffer* buffer_;
+};
+
} // namespace io
} // namespace aapt
-#endif // AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#endif // AAPT_IO_BIGBUFFERSTREAM_H
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 09dc7ea..db91a77 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -28,12 +28,16 @@
namespace io {
// Interface for a block of contiguous memory. An instance of this interface owns the data.
-class IData : public InputStream {
+class IData : public KnownSizeInputStream {
public:
virtual ~IData() = default;
virtual const void* data() const = 0;
virtual size_t size() const = 0;
+
+ virtual size_t TotalSize() const override {
+ return size();
+ }
};
class DataSegment : public IData {
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
index ee73728..b4f1ff3 100644
--- a/tools/aapt2/io/File.cpp
+++ b/tools/aapt2/io/File.cpp
@@ -39,5 +39,9 @@
return {};
}
+std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() {
+ return OpenAsData();
+}
+
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 7ef6d88..f06e28c 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -43,6 +43,8 @@
// Returns nullptr on failure.
virtual std::unique_ptr<IData> OpenAsData() = 0;
+ virtual std::unique_ptr<io::InputStream> OpenInputStream() = 0;
+
// Returns the source of this file. This is for presentation to the user and
// may not be a valid file system path (for example, it may contain a '@' sign to separate
// the files within a ZIP archive from the path to the containing ZIP archive.
@@ -71,8 +73,11 @@
: file_(file), offset_(offset), len_(len) {}
std::unique_ptr<IData> OpenAsData() override;
+ std::unique_ptr<io::InputStream> OpenInputStream() override;
- const Source& GetSource() const override { return file_->GetSource(); }
+ const Source& GetSource() const override {
+ return file_->GetSource();
+ }
private:
DISALLOW_COPY_AND_ASSIGN(FileSegment);
diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp
deleted file mode 100644
index 07dbb5a..0000000
--- a/tools/aapt2/io/FileInputStream.cpp
+++ /dev/null
@@ -1,102 +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.
- */
-
-#include "io/FileInputStream.h"
-
-#include <errno.h> // for errno
-#include <fcntl.h> // for O_RDONLY
-#include <unistd.h> // for read
-
-#include "android-base/errors.h"
-#include "android-base/file.h" // for O_BINARY
-#include "android-base/macros.h"
-#include "android-base/utf8.h"
-
-using ::android::base::SystemErrorCodeToString;
-
-namespace aapt {
-namespace io {
-
-FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
- : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
- buffer_capacity) {
-}
-
-FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
- : fd_(fd),
- buffer_capacity_(buffer_capacity),
- buffer_offset_(0u),
- buffer_size_(0u),
- total_byte_count_(0u) {
- if (fd_ == -1) {
- error_ = SystemErrorCodeToString(errno);
- } else {
- buffer_.reset(new uint8_t[buffer_capacity_]);
- }
-}
-
-bool FileInputStream::Next(const void** data, size_t* size) {
- if (HadError()) {
- return false;
- }
-
- // Deal with any remaining bytes after BackUp was called.
- if (buffer_offset_ != buffer_size_) {
- *data = buffer_.get() + buffer_offset_;
- *size = buffer_size_ - buffer_offset_;
- total_byte_count_ += buffer_size_ - buffer_offset_;
- buffer_offset_ = buffer_size_;
- return true;
- }
-
- ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
- if (n < 0) {
- error_ = SystemErrorCodeToString(errno);
- fd_.reset();
- return false;
- }
-
- buffer_size_ = static_cast<size_t>(n);
- buffer_offset_ = buffer_size_;
- total_byte_count_ += buffer_size_;
-
- *data = buffer_.get();
- *size = buffer_size_;
- return buffer_size_ != 0u;
-}
-
-void FileInputStream::BackUp(size_t count) {
- if (count > buffer_offset_) {
- count = buffer_offset_;
- }
- buffer_offset_ -= count;
- total_byte_count_ -= count;
-}
-
-size_t FileInputStream::ByteCount() const {
- return total_byte_count_;
-}
-
-bool FileInputStream::HadError() const {
- return !error_.empty();
-}
-
-std::string FileInputStream::GetError() const {
- return error_;
-}
-
-} // namespace io
-} // namespace aapt
diff --git a/tools/aapt2/io/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
new file mode 100644
index 0000000..2f7a4b3
--- /dev/null
+++ b/tools/aapt2/io/FileStream.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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 "io/FileStream.h"
+
+#include <errno.h> // for errno
+#include <fcntl.h> // for O_RDONLY
+#include <unistd.h> // for read
+
+#include "android-base/errors.h"
+#include "android-base/file.h" // for O_BINARY
+#include "android-base/macros.h"
+#include "android-base/utf8.h"
+
+using ::android::base::SystemErrorCodeToString;
+
+namespace aapt {
+namespace io {
+
+FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
+ : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
+ buffer_capacity) {
+}
+
+FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
+ : fd_(fd),
+ buffer_capacity_(buffer_capacity),
+ buffer_offset_(0u),
+ buffer_size_(0u),
+ total_byte_count_(0u) {
+ if (fd_ == -1) {
+ error_ = SystemErrorCodeToString(errno);
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+bool FileInputStream::Next(const void** data, size_t* size) {
+ if (HadError()) {
+ return false;
+ }
+
+ // Deal with any remaining bytes after BackUp was called.
+ if (buffer_offset_ != buffer_size_) {
+ *data = buffer_.get() + buffer_offset_;
+ *size = buffer_size_ - buffer_offset_;
+ total_byte_count_ += buffer_size_ - buffer_offset_;
+ buffer_offset_ = buffer_size_;
+ return true;
+ }
+
+ ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
+ if (n < 0) {
+ error_ = SystemErrorCodeToString(errno);
+ fd_.reset();
+ buffer_.reset();
+ return false;
+ }
+
+ buffer_size_ = static_cast<size_t>(n);
+ buffer_offset_ = buffer_size_;
+ total_byte_count_ += buffer_size_;
+
+ *data = buffer_.get();
+ *size = buffer_size_;
+ return buffer_size_ != 0u;
+}
+
+void FileInputStream::BackUp(size_t count) {
+ if (count > buffer_offset_) {
+ count = buffer_offset_;
+ }
+ buffer_offset_ -= count;
+ total_byte_count_ -= count;
+}
+
+size_t FileInputStream::ByteCount() const {
+ return total_byte_count_;
+}
+
+bool FileInputStream::HadError() const {
+ return fd_ == -1;
+}
+
+std::string FileInputStream::GetError() const {
+ return error_;
+}
+
+FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity)
+ : FileOutputStream(::android::base::utf8::open(path.c_str(), mode), buffer_capacity) {
+}
+
+FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
+ : fd_(fd), buffer_capacity_(buffer_capacity), buffer_offset_(0u), total_byte_count_(0u) {
+ if (fd_ == -1) {
+ error_ = SystemErrorCodeToString(errno);
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+FileOutputStream::~FileOutputStream() {
+ // Flush the buffer.
+ Flush();
+}
+
+bool FileOutputStream::Next(void** data, size_t* size) {
+ if (fd_ == -1 || HadError()) {
+ return false;
+ }
+
+ if (buffer_offset_ == buffer_capacity_) {
+ if (!FlushImpl()) {
+ return false;
+ }
+ }
+
+ const size_t buffer_size = buffer_capacity_ - buffer_offset_;
+ *data = buffer_.get() + buffer_offset_;
+ *size = buffer_size;
+ total_byte_count_ += buffer_size;
+ buffer_offset_ = buffer_capacity_;
+ return true;
+}
+
+void FileOutputStream::BackUp(size_t count) {
+ if (count > buffer_offset_) {
+ count = buffer_offset_;
+ }
+ buffer_offset_ -= count;
+ total_byte_count_ -= count;
+}
+
+size_t FileOutputStream::ByteCount() const {
+ return total_byte_count_;
+}
+
+bool FileOutputStream::Flush() {
+ if (!HadError()) {
+ return FlushImpl();
+ }
+ return false;
+}
+
+bool FileOutputStream::FlushImpl() {
+ ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
+ if (n < 0) {
+ error_ = SystemErrorCodeToString(errno);
+ fd_.reset();
+ buffer_.reset();
+ return false;
+ }
+
+ buffer_offset_ = 0u;
+ return true;
+}
+
+bool FileOutputStream::HadError() const {
+ return fd_ == -1;
+}
+
+std::string FileOutputStream::GetError() const {
+ return error_;
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileStream.h
similarity index 60%
rename from tools/aapt2/io/FileInputStream.h
rename to tools/aapt2/io/FileStream.h
index 6beb9a1..3b07667 100644
--- a/tools/aapt2/io/FileInputStream.h
+++ b/tools/aapt2/io/FileStream.h
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-#ifndef AAPT_IO_FILEINPUTSTREAM_H
-#define AAPT_IO_FILEINPUTSTREAM_H
+#ifndef AAPT_IO_FILESTREAM_H
+#define AAPT_IO_FILESTREAM_H
#include "io/Io.h"
#include <memory>
#include <string>
+#include "android-base/file.h" // for O_BINARY
#include "android-base/macros.h"
#include "android-base/unique_fd.h"
@@ -57,7 +58,43 @@
size_t total_byte_count_;
};
+class FileOutputStream : public OutputStream {
+ public:
+ explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY,
+ size_t buffer_capacity = 4096);
+
+ // Takes ownership of `fd`.
+ explicit FileOutputStream(int fd, size_t buffer_capacity = 4096);
+
+ ~FileOutputStream();
+
+ bool Next(void** data, size_t* size) override;
+
+ // Immediately flushes out the contents of the buffer to disk.
+ bool Flush();
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ std::string GetError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileOutputStream);
+
+ bool FlushImpl();
+
+ android::base::unique_fd fd_;
+ std::string error_;
+ std::unique_ptr<uint8_t[]> buffer_;
+ size_t buffer_capacity_;
+ size_t buffer_offset_;
+ size_t total_byte_count_;
+};
+
} // namespace io
} // namespace aapt
-#endif // AAPT_IO_FILEINPUTSTREAM_H
+#endif // AAPT_IO_FILESTREAM_H
diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
similarity index 68%
rename from tools/aapt2/io/FileInputStream_test.cpp
rename to tools/aapt2/io/FileStream_test.cpp
index 7314ab7..68c3cb1 100644
--- a/tools/aapt2/io/FileInputStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "io/FileInputStream.h"
+#include "io/FileStream.h"
#include "android-base/macros.h"
#include "android-base/test_utils.h"
@@ -36,7 +36,7 @@
lseek64(file.fd, 0, SEEK_SET);
// Use a small buffer size so that we can call Next() a few times.
- FileInputStream in(file.fd, 10u);
+ FileInputStream in(file.release(), 10u);
ASSERT_FALSE(in.HadError());
EXPECT_THAT(in.ByteCount(), Eq(0u));
@@ -83,5 +83,47 @@
EXPECT_FALSE(in.HadError());
}
+TEST(FileOutputStreamTest, NextAndBackup) {
+ const std::string input = "this is a cool string";
+
+ TemporaryFile file;
+ int fd = file.release();
+
+ // FileOutputStream takes ownership.
+ FileOutputStream out(fd, 10u);
+ ASSERT_FALSE(out.HadError());
+ EXPECT_THAT(out.ByteCount(), Eq(0u));
+
+ char* buffer;
+ size_t size;
+ ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(out.ByteCount(), Eq(10u));
+ memcpy(buffer, input.c_str(), size);
+
+ ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(out.ByteCount(), Eq(20u));
+ memcpy(buffer, input.c_str() + 10u, size);
+
+ ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(out.ByteCount(), Eq(30u));
+ buffer[0] = input[20u];
+ out.BackUp(size - 1);
+ EXPECT_THAT(out.ByteCount(), Eq(21u));
+
+ ASSERT_TRUE(out.Flush());
+
+ lseek64(fd, 0, SEEK_SET);
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(fd, &actual));
+ EXPECT_THAT(actual, StrEq(input));
+}
+
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 027cbd0..1387d22 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -20,11 +20,12 @@
#include "utils/FileMap.h"
#include "Source.h"
+#include "io/FileStream.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
-using android::StringPiece;
+using ::android::StringPiece;
namespace aapt {
namespace io {
@@ -42,12 +43,20 @@
return {};
}
-const Source& RegularFile::GetSource() const { return source_; }
+std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() {
+ return util::make_unique<FileInputStream>(source_.path);
+}
+
+const Source& RegularFile::GetSource() const {
+ return source_;
+}
FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
-bool FileCollectionIterator::HasNext() { return current_ != end_; }
+bool FileCollectionIterator::HasNext() {
+ return current_ != end_;
+}
IFile* FileCollectionIterator::Next() {
IFile* result = current_->second.get();
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index dfd3717..6be8807 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -24,17 +24,18 @@
namespace aapt {
namespace io {
-/**
- * A regular file from the file system. Uses mmap to open the data.
- */
+// A regular file from the file system. Uses mmap to open the data.
class RegularFile : public IFile {
public:
explicit RegularFile(const Source& source);
std::unique_ptr<IData> OpenAsData() override;
+ std::unique_ptr<io::InputStream> OpenInputStream() override;
const Source& GetSource() const override;
private:
+ DISALLOW_COPY_AND_ASSIGN(RegularFile);
+
Source source_;
};
@@ -48,23 +49,26 @@
io::IFile* Next() override;
private:
+ DISALLOW_COPY_AND_ASSIGN(FileCollectionIterator);
+
std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
};
-/**
- * An IFileCollection representing the file system.
- */
+// An IFileCollection representing the file system.
class FileCollection : public IFileCollection {
public:
- /**
- * Adds a file located at path. Returns the IFile representation of that file.
- */
+ FileCollection() = default;
+
+ // Adds a file located at path. Returns the IFile representation of that file.
IFile* InsertFile(const android::StringPiece& path);
IFile* FindFile(const android::StringPiece& path) override;
std::unique_ptr<IFileCollectionIterator> Iterator() override;
private:
+ DISALLOW_COPY_AND_ASSIGN(FileCollection);
+
friend class FileCollectionIterator;
+
std::map<std::string, std::unique_ptr<IFile>> files_;
};
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index a656740..e1df23a6 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -58,6 +58,12 @@
virtual bool HadError() const = 0;
};
+// A sub-InputStream interface that knows the total size of its stream.
+class KnownSizeInputStream : public InputStream {
+ public:
+ virtual size_t TotalSize() const = 0;
+};
+
// OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
// with added error handling methods to better report issues.
class OutputStream {
diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringInputStream.cpp
deleted file mode 100644
index 51a18a7..0000000
--- a/tools/aapt2/io/StringInputStream.cpp
+++ /dev/null
@@ -1,50 +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.
- */
-
-#include "io/StringInputStream.h"
-
-using ::android::StringPiece;
-
-namespace aapt {
-namespace io {
-
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
-}
-
-bool StringInputStream::Next(const void** data, size_t* size) {
- if (offset_ == str_.size()) {
- return false;
- }
-
- *data = str_.data() + offset_;
- *size = str_.size() - offset_;
- offset_ = str_.size();
- return true;
-}
-
-void StringInputStream::BackUp(size_t count) {
- if (count > offset_) {
- count = offset_;
- }
- offset_ -= count;
-}
-
-size_t StringInputStream::ByteCount() const {
- return offset_;
-}
-
-} // namespace io
-} // namespace aapt
diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringInputStream.h
deleted file mode 100644
index ff5b112..0000000
--- a/tools/aapt2/io/StringInputStream.h
+++ /dev/null
@@ -1,56 +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.
- */
-
-#ifndef AAPT_IO_STRINGINPUTSTREAM_H
-#define AAPT_IO_STRINGINPUTSTREAM_H
-
-#include "io/Io.h"
-
-#include "android-base/macros.h"
-#include "androidfw/StringPiece.h"
-
-namespace aapt {
-namespace io {
-
-class StringInputStream : public InputStream {
- public:
- explicit StringInputStream(const android::StringPiece& str);
-
- bool Next(const void** data, size_t* size) override;
-
- void BackUp(size_t count) override;
-
- size_t ByteCount() const override;
-
- inline bool HadError() const override {
- return false;
- }
-
- inline std::string GetError() const override {
- return {};
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(StringInputStream);
-
- android::StringPiece str_;
- size_t offset_;
-};
-
-} // namespace io
-} // namespace aapt
-
-#endif // AAPT_IO_STRINGINPUTSTREAM_H
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
new file mode 100644
index 0000000..4ca04a8
--- /dev/null
+++ b/tools/aapt2/io/StringStream.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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 "io/StringStream.h"
+
+using ::android::StringPiece;
+
+namespace aapt {
+namespace io {
+
+StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+}
+
+bool StringInputStream::Next(const void** data, size_t* size) {
+ if (offset_ == str_.size()) {
+ return false;
+ }
+
+ *data = str_.data() + offset_;
+ *size = str_.size() - offset_;
+ offset_ = str_.size();
+ return true;
+}
+
+void StringInputStream::BackUp(size_t count) {
+ if (count > offset_) {
+ offset_ = 0u;
+ } else {
+ offset_ -= count;
+ }
+}
+
+size_t StringInputStream::ByteCount() const {
+ return offset_;
+}
+
+size_t StringInputStream::TotalSize() const {
+ return str_.size();
+}
+
+StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity)
+ : str_(str),
+ buffer_capacity_(buffer_capacity),
+ buffer_offset_(0u),
+ buffer_(new char[buffer_capacity]) {
+}
+
+StringOutputStream::~StringOutputStream() {
+ Flush();
+}
+
+bool StringOutputStream::Next(void** data, size_t* size) {
+ if (buffer_offset_ == buffer_capacity_) {
+ FlushImpl();
+ }
+
+ *data = buffer_.get() + buffer_offset_;
+ *size = buffer_capacity_ - buffer_offset_;
+ buffer_offset_ = buffer_capacity_;
+ return true;
+}
+
+void StringOutputStream::BackUp(size_t count) {
+ if (count > buffer_offset_) {
+ buffer_offset_ = 0u;
+ } else {
+ buffer_offset_ -= count;
+ }
+}
+
+size_t StringOutputStream::ByteCount() const {
+ return str_->size() + buffer_offset_;
+}
+
+void StringOutputStream::Flush() {
+ if (buffer_offset_ != 0u) {
+ FlushImpl();
+ }
+}
+
+void StringOutputStream::FlushImpl() {
+ str_->append(buffer_.get(), buffer_offset_);
+ buffer_offset_ = 0u;
+}
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
new file mode 100644
index 0000000..f29890a
--- /dev/null
+++ b/tools/aapt2/io/StringStream.h
@@ -0,0 +1,93 @@
+/*
+ * 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 AAPT_IO_STRINGSTREAM_H
+#define AAPT_IO_STRINGSTREAM_H
+
+#include "io/Io.h"
+
+#include <memory>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+namespace aapt {
+namespace io {
+
+class StringInputStream : public KnownSizeInputStream {
+ public:
+ explicit StringInputStream(const android::StringPiece& str);
+
+ bool Next(const void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ inline bool HadError() const override {
+ return false;
+ }
+
+ inline std::string GetError() const override {
+ return {};
+ }
+
+ size_t TotalSize() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StringInputStream);
+
+ android::StringPiece str_;
+ size_t offset_;
+};
+
+class StringOutputStream : public OutputStream {
+ public:
+ explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u);
+
+ ~StringOutputStream();
+
+ bool Next(void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ void Flush();
+
+ size_t ByteCount() const override;
+
+ inline bool HadError() const override {
+ return false;
+ }
+
+ inline std::string GetError() const override {
+ return {};
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StringOutputStream);
+
+ void FlushImpl();
+
+ std::string* str_;
+ size_t buffer_capacity_;
+ size_t buffer_offset_;
+ std::unique_ptr<char[]> buffer_;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_STRINGSTREAM_H
diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringStream_test.cpp
similarity index 86%
rename from tools/aapt2/io/StringInputStream_test.cpp
rename to tools/aapt2/io/StringStream_test.cpp
index cc57bc4..fb43fb7 100644
--- a/tools/aapt2/io/StringInputStream_test.cpp
+++ b/tools/aapt2/io/StringStream_test.cpp
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
+
+#include "io/Util.h"
#include "test/Test.h"
@@ -68,5 +70,16 @@
EXPECT_THAT(in.ByteCount(), Eq(input.size()));
}
+TEST(StringOutputStreamTest, NextAndBackUp) {
+ std::string input = "hello this is a string";
+ std::string output;
+
+ StringInputStream in(input);
+ StringOutputStream out(&output, 10u);
+ ASSERT_TRUE(Copy(&out, &in));
+ out.Flush();
+ EXPECT_THAT(output, StrEq(input));
+}
+
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index 15114e8..d270340 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -18,6 +18,8 @@
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
namespace aapt {
namespace io {
@@ -91,5 +93,10 @@
return !in->HadError();
}
+bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
+ OutputStreamAdaptor adaptor(out);
+ return Copy(&adaptor, in);
+}
+
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 02ee876..1e48508 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -42,6 +42,81 @@
// Copies the data from in to out. Returns false if there was an error.
// If there was an error, check the individual streams' HadError/GetError methods.
bool Copy(OutputStream* out, InputStream* in);
+bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
+
+class OutputStreamAdaptor : public io::OutputStream {
+ public:
+ explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) {
+ }
+
+ bool Next(void** data, size_t* size) override {
+ int out_size;
+ bool result = out_->Next(data, &out_size);
+ *size = static_cast<size_t>(out_size);
+ if (!result) {
+ error_ocurred_ = true;
+ }
+ return result;
+ }
+
+ void BackUp(size_t count) override {
+ out_->BackUp(static_cast<int>(count));
+ }
+
+ size_t ByteCount() const override {
+ return static_cast<size_t>(out_->ByteCount());
+ }
+
+ bool HadError() const override {
+ return error_ocurred_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OutputStreamAdaptor);
+
+ ::google::protobuf::io::ZeroCopyOutputStream* out_;
+ bool error_ocurred_ = false;
+};
+
+class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream {
+ public:
+ explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) {
+ }
+
+ bool Next(const void** data, int* size) override {
+ size_t out_size;
+ bool result = in_->Next(data, &out_size);
+ *size = static_cast<int>(out_size);
+ return result;
+ }
+
+ void BackUp(int count) override {
+ in_->BackUp(static_cast<size_t>(count));
+ }
+
+ bool Skip(int count) override {
+ const void* data;
+ int size;
+ while (Next(&data, &size)) {
+ if (size > count) {
+ BackUp(size - count);
+ return true;
+ } else {
+ count -= size;
+ }
+ }
+ return false;
+ }
+
+ ::google::protobuf::int64 ByteCount() const override {
+ return static_cast<::google::protobuf::int64>(in_->ByteCount());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor);
+
+ io::InputStream* in_;
+};
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 6494d2d..269b6c5 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -22,7 +22,7 @@
#include "Source.h"
#include "util/Util.h"
-using android::StringPiece;
+using ::android::StringPiece;
namespace aapt {
namespace io {
@@ -57,7 +57,13 @@
}
}
-const Source& ZipFile::GetSource() const { return source_; }
+std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() {
+ return OpenAsData();
+}
+
+const Source& ZipFile::GetSource() const {
+ return source_;
+}
bool ZipFile::WasCompressed() {
return zip_entry_.method != kCompressStored;
@@ -67,7 +73,9 @@
ZipFileCollection* collection)
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
-bool ZipFileCollectionIterator::HasNext() { return current_ != end_; }
+bool ZipFileCollectionIterator::HasNext() {
+ return current_ != end_;
+}
IFile* ZipFileCollectionIterator::Next() {
IFile* result = current_->get();
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 56c74e3..8381259 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -28,23 +28,20 @@
namespace aapt {
namespace io {
-/**
- * An IFile representing a file within a ZIP archive. If the file is compressed,
- * it is uncompressed
- * and copied into memory when opened. Otherwise it is mmapped from the ZIP
- * archive.
- */
+// An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+// and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
class ZipFile : public IFile {
public:
- ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
+ ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const Source& source);
std::unique_ptr<IData> OpenAsData() override;
+ std::unique_ptr<io::InputStream> OpenInputStream() override;
const Source& GetSource() const override;
bool WasCompressed() override;
private:
- ZipArchiveHandle zip_handle_;
- ZipEntry zip_entry_;
+ ::ZipArchiveHandle zip_handle_;
+ ::ZipEntry zip_entry_;
Source source_;
};
@@ -61,9 +58,7 @@
std::vector<std::unique_ptr<IFile>>::const_iterator current_, end_;
};
-/**
- * An IFileCollection that represents a ZIP archive and the entries within it.
- */
+// An IFileCollection that represents a ZIP archive and the entries within it.
class ZipFileCollection : public IFileCollection {
public:
static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 93c904f..21c6b11 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,13 +37,12 @@
CHECK(master_package_ != nullptr) << "package name or ID already taken";
}
-bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) {
- return MergeImpl(src, table, collection, false /*overlay*/, true /*allow_new*/);
-}
-
-bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table,
- io::IFileCollection* collection) {
- return MergeImpl(src, table, collection, true /*overlay*/, options_.auto_add_overlay);
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
+ io::IFileCollection* collection) {
+ // We allow adding new resources if this is not an overlay, or if the options allow overlays
+ // to add new resources.
+ return MergeImpl(src, table, collection, overlay,
+ options_.auto_add_overlay || !overlay /*allow_new*/);
}
// This will merge packages with the same package name (or no package name).
@@ -322,17 +321,20 @@
util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath));
new_file_ref->SetComment(file_ref.GetComment());
new_file_ref->SetSource(file_ref.GetSource());
+ new_file_ref->type = file_ref.type;
+ new_file_ref->file = file_ref.file;
return new_file_ref;
}
return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool));
}
-bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) {
+bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) {
ResourceTable table;
std::string path = ResourceUtils::BuildResourceFileName(file_desc);
std::unique_ptr<FileReference> file_ref =
util::make_unique<FileReference>(table.string_pool.MakeRef(path));
file_ref->SetSource(file_desc.source);
+ file_ref->type = file_desc.type;
file_ref->file = file;
ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
@@ -341,17 +343,8 @@
->FindOrCreateValue(file_desc.config, {})
->value = std::move(file_ref);
- return DoMerge(file->GetSource(), &table, pkg, false /* mangle */,
- overlay /* overlay */, true /* allow_new */, {});
-}
-
-bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) {
- return MergeFileImpl(file_desc, file, false /* overlay */);
-}
-
-bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc,
- io::IFile* file) {
- return MergeFileImpl(file_desc, file, true /* overlay */);
+ return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
+ true /* allow_new */, {});
}
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 81518ff..d024aa4 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -59,24 +59,18 @@
}
// Merges resources from the same or empty package. This is for local sources.
+ // If overlay is true, the resources are treated as overlays.
// An io::IFileCollection is optional and used to find the referenced Files and process them.
- bool Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection = nullptr);
-
- // Merges resources from an overlay ResourceTable.
- // An io::IFileCollection is optional and used to find the referenced Files and process them.
- bool MergeOverlay(const Source& src, ResourceTable* table,
- io::IFileCollection* collection = nullptr);
+ bool Merge(const Source& src, ResourceTable* table, bool overlay,
+ io::IFileCollection* collection = nullptr);
// Merges resources from the given package, mangling the name. This is for static libraries.
// An io::IFileCollection is needed in order to find the referenced Files and process them.
bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
io::IFileCollection* collection);
- // Merges a compiled file that belongs to this same or empty package. This is for local sources.
- bool MergeFile(const ResourceFile& fileDesc, io::IFile* file);
-
- // Merges a compiled file from an overlay, overriding an existing definition.
- bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file);
+ // Merges a compiled file that belongs to this same or empty package.
+ bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
private:
DISALLOW_COPY_AND_ASSIGN(TableMerger);
@@ -91,9 +85,6 @@
ResourceTablePackage* master_package_;
std::set<std::string> merged_packages_;
- bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file,
- bool overlay);
-
bool MergeImpl(const Source& src, ResourceTable* src_table,
io::IFileCollection* collection, bool overlay, bool allow_new);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 45b01a4..3499809 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -69,7 +69,7 @@
TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
io::FileCollection collection;
- ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
@@ -98,7 +98,7 @@
file_desc.source = Source("res/layout-hdpi/main.xml");
test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat");
- ASSERT_TRUE(merger.MergeFile(file_desc, &test_file));
+ ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &test_file));
FileReference* file = test::GetValueForConfig<FileReference>(
&final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4"));
@@ -117,8 +117,8 @@
test::TestFile file_a("path/to/fileA.xml.flat");
test::TestFile file_b("path/to/fileB.xml.flat");
- ASSERT_TRUE(merger.MergeFile(file_desc, &file_a));
- ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b));
+ ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &file_a));
+ ASSERT_TRUE(merger.MergeFile(file_desc, true /*overlay*/, &file_b));
}
TEST_F(TableMergerTest, MergeFileReferences) {
@@ -138,7 +138,7 @@
io::FileCollection collection;
collection.InsertFile("res/xml/file.xml");
- ASSERT_TRUE(merger.Merge({}, table_a.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
@@ -167,8 +167,8 @@
options.auto_add_overlay = false;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, base.get()));
- ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
BinaryPrimitive* foo = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo");
ASSERT_THAT(foo,
@@ -194,8 +194,8 @@
options.auto_add_overlay = false;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, base.get()));
- ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
}
TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
@@ -217,8 +217,8 @@
options.auto_add_overlay = false;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, base.get()));
- ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
}
TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
@@ -240,8 +240,8 @@
options.auto_add_overlay = false;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, base.get()));
- ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
}
TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
@@ -259,8 +259,8 @@
options.auto_add_overlay = false;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, table_a.get()));
- ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
@@ -277,8 +277,8 @@
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, table_a.get()));
- ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
@@ -295,8 +295,8 @@
options.auto_add_overlay = false;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, table_a.get()));
- ASSERT_FALSE(merger.MergeOverlay({}, table_b.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_FALSE(merger.Merge({}, table_b.get(), true /*overlay*/));
}
TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
@@ -337,8 +337,8 @@
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
- ASSERT_TRUE(merger.Merge({}, table_a.get()));
- ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/));
Styleable* styleable = test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo");
ASSERT_THAT(styleable, NotNull());
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index e658664..5a62e97 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -19,7 +19,7 @@
#include "android-base/logging.h"
#include "androidfw/StringPiece.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
#include "test/Common.h"
#include "util/Util.h"
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 61d0563..4e318a9 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -90,6 +90,10 @@
return {};
}
+ std::unique_ptr<io::InputStream> OpenInputStream() override {
+ return OpenAsData();
+ }
+
const Source& GetSource() const override {
return source_;
}
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index b3e0a92..3522506 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -215,8 +215,8 @@
return {};
}
}
- return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{},
- std::move(stack.root));
+ return util::make_unique<XmlResource>(ResourceFile{{}, {}, ResourceFile::Type::kUnknown, source},
+ StringPool{}, std::move(stack.root));
}
static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) {
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 4ba0443..34e6d3f 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -19,7 +19,7 @@
#include <string>
#include "format/binary/XmlFlattener.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
#include "test/Test.h"
using ::aapt::io::StringInputStream;
diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 681d9d4..5304bde 100644
--- a/tools/aapt2/xml/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -18,43 +18,49 @@
#include "androidfw/StringPiece.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
#include "test/Test.h"
using ::aapt::io::StringInputStream;
using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::StrEq;
+
+using Event = ::aapt::xml::XmlPullParser::Event;
namespace aapt {
+namespace xml {
TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
std::string str =
R"(<?xml version="1.0" encoding="utf-8"?>
<a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)";
StringInputStream input(str);
- xml::XmlPullParser parser(&input);
+ XmlPullParser parser(&input);
const size_t depth_outer = parser.depth();
- ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
+ ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_outer));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
- EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name()));
+ EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+ EXPECT_THAT(parser.element_name(), StrEq("a"));
const size_t depth_a = parser.depth();
- ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
- EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name()));
+ ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_a));
+ EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+ EXPECT_THAT(parser.element_name(), StrEq("b"));
const size_t depth_b = parser.depth();
- ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
- EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name()));
+ ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b));
+ EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+ EXPECT_THAT(parser.element_name(), StrEq("c"));
- ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
- EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
- EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name()));
+ ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b));
+ EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+ EXPECT_THAT(parser.element_name(), StrEq("e"));
- ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
- EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event());
+ ASSERT_FALSE(XmlPullParser::NextChildNode(&parser, depth_outer));
+ EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kEndDocument));
}
+} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index c1186e8..0a622b2 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -16,20 +16,20 @@
#include "xml/XmlUtil.h"
+#include <algorithm>
#include <string>
#include "util/Maybe.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
-using android::StringPiece;
+using ::android::StringPiece;
namespace aapt {
namespace xml {
-std::string BuildPackageNamespace(const StringPiece& package,
- bool private_reference) {
- std::string result =
- private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
+std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+ std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
result.append(package.data(), package.size());
return result;
}
@@ -39,8 +39,7 @@
if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) {
StringPiece schema_prefix = kSchemaPublicPrefix;
StringPiece package = namespace_uri;
- package = package.substr(schema_prefix.size(),
- package.size() - schema_prefix.size());
+ package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size());
if (package.empty()) {
return {};
}
@@ -49,8 +48,7 @@
} else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
StringPiece schema_prefix = kSchemaPrivatePrefix;
StringPiece package = namespace_uri;
- package = package.substr(schema_prefix.size(),
- package.size() - schema_prefix.size());
+ package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size());
if (package.empty()) {
return {};
}
@@ -76,5 +74,33 @@
}
}
+namespace {
+
+class ToolsNamespaceRemover : public Visitor {
+ public:
+ using Visitor::Visit;
+
+ void Visit(Element* el) override {
+ auto new_end =
+ std::remove_if(el->namespace_decls.begin(), el->namespace_decls.end(),
+ [](const NamespaceDecl& decl) -> bool { return decl.uri == kSchemaTools; });
+ el->namespace_decls.erase(new_end, el->namespace_decls.end());
+
+ auto new_attr_end = std::remove_if(
+ el->attributes.begin(), el->attributes.end(),
+ [](const Attribute& attr) -> bool { return attr.namespace_uri == kSchemaTools; });
+ el->attributes.erase(new_attr_end, el->attributes.end());
+
+ Visitor::Visit(el);
+ }
+};
+
+} // namespace
+
+void StripAndroidStudioAttributes(Element* el) {
+ ToolsNamespaceRemover remover;
+ el->Accept(&remover);
+}
+
} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 4eb359a..592a604 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -78,6 +78,12 @@
// package declaration was private.
void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref);
+class Element;
+
+// Strips out any attributes in the http://schemas.android.com/tools namespace, which is owned by
+// Android Studio and should not make it to the final APK.
+void StripAndroidStudioAttributes(Element* el);
+
} // namespace xml
} // namespace aapt
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
index bb5f4d6..3f65151 100644
--- a/tools/locked_region_code_injection/Android.mk
+++ b/tools/locked_region_code_injection/Android.mk
@@ -6,10 +6,10 @@
LOCAL_MODULE := lockedregioncodeinjection
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-6.0_BETA \
- asm-commons-6.0_BETA \
- asm-tree-6.0_BETA \
- asm-analysis-6.0_BETA \
+ asm-6.0 \
+ asm-commons-6.0 \
+ asm-tree-6.0 \
+ asm-analysis-6.0 \
guava-21.0 \
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
index fb1636f..578dd1e 100644
--- a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
+++ b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
@@ -26,7 +26,12 @@
oneway interface IRttCallback
{
/**
- * Service to manager callback providing RTT status and results.
+ * Service to manager callback indicating failure.
*/
- void onRangingResults(int status, in List<RangingResult> results);
+ void onRangingFailure(int status);
+
+ /**
+ * Service to manager callback indicating success and providing results.
+ */
+ void onRangingResults(in List<RangingResult> results);
}
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 997b680..a396281 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -17,13 +17,22 @@
package android.net.wifi.rtt;
import android.net.wifi.ScanResult;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareManager;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import libcore.util.HexEncoding;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.StringJoiner;
/**
@@ -33,8 +42,8 @@
* {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
* <p>
* The ranging request is a batch request - specifying a set of devices (specified using
- * {@link RangingRequest.Builder#addAp(ScanResult)} and
- * {@link RangingRequest.Builder#addAps(List)}).
+ * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
+ * {@link RangingRequest.Builder#addAccessPoints(List)}).
*
* @hide RTT_API
*/
@@ -44,8 +53,8 @@
/**
* Returns the maximum number of peers to range which can be specified in a single {@code
* RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
- * through {@link RangingRequest.Builder#addAp(ScanResult)} or
- * {@link RangingRequest.Builder#addAps(List)}.
+ * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
+ * {@link RangingRequest.Builder#addAccessPoints(List)}.
*
* @return Maximum number of peers.
*/
@@ -94,11 +103,34 @@
}
/** @hide */
- public void enforceValidity() {
+ public void enforceValidity(boolean awareSupported) {
if (mRttPeers.size() > MAX_PEERS) {
throw new IllegalArgumentException(
"Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
}
+
+ for (RttPeer peer: mRttPeers) {
+ if (peer instanceof RttPeerAp) {
+ RttPeerAp apPeer = (RttPeerAp) peer;
+ if (apPeer.scanResult == null || apPeer.scanResult.BSSID == null) {
+ throw new IllegalArgumentException("Invalid AP peer specification");
+ }
+ } else if (peer instanceof RttPeerAware) {
+ if (!awareSupported) {
+ throw new IllegalArgumentException(
+ "Request contains Aware peers - but Aware isn't supported on this "
+ + "device");
+ }
+
+ RttPeerAware awarePeer = (RttPeerAware) peer;
+ if (awarePeer.peerMacAddress == null && awarePeer.peerHandle == null) {
+ throw new IllegalArgumentException("Invalid Aware peer specification");
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Request contains unknown peer specification types");
+ }
+ }
}
/**
@@ -116,7 +148,7 @@
* @return The builder to facilitate chaining
* {@code builder.setXXX(..).setXXX(..)}.
*/
- public Builder addAp(ScanResult apInfo) {
+ public Builder addAccessPoint(ScanResult apInfo) {
if (apInfo == null) {
throw new IllegalArgumentException("Null ScanResult!");
}
@@ -133,17 +165,55 @@
* @return The builder to facilitate chaining
* {@code builder.setXXX(..).setXXX(..)}.
*/
- public Builder addAps(List<ScanResult> apInfos) {
+ public Builder addAccessPoints(List<ScanResult> apInfos) {
if (apInfos == null) {
throw new IllegalArgumentException("Null list of ScanResults!");
}
for (ScanResult scanResult : apInfos) {
- addAp(scanResult);
+ addAccessPoint(scanResult);
}
return this;
}
/**
+ * Add the device specified by the {@code peerMacAddress} to the list of devices with
+ * which to measure range.
+ *
+ * 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}.
+ *
+ * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addWifiAwarePeer(byte[] peerMacAddress) {
+ mRttPeers.add(new RttPeerAware(peerMacAddress));
+ return this;
+ }
+
+ /**
+ * Add a device specified by a {@link PeerHandle} to the list of devices with which to
+ * measure range.
+ *
+ * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
+ * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
+ *
+ * Note: in order to use this API the device must support Wi-Fi Aware
+ * {@link android.net.wifi.aware}.
+ *
+ * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addWifiAwarePeer(PeerHandle peerHandle) {
+ mRttPeers.add(new RttPeerAware(peerHandle));
+ return this;
+ }
+
+ /**
* Build {@link RangingRequest} given the current configurations made on the
* builder.
*/
@@ -234,4 +304,89 @@
return scanResult.hashCode();
}
}
-}
\ No newline at end of file
+
+ /** @hide */
+ public static class RttPeerAware implements RttPeer, Parcelable {
+ public PeerHandle peerHandle;
+ public byte[] peerMacAddress;
+
+ public RttPeerAware(PeerHandle peerHandle) {
+ if (peerHandle == null) {
+ throw new IllegalArgumentException("Null peerHandle");
+ }
+ this.peerHandle = peerHandle;
+ peerMacAddress = null;
+ }
+
+ public RttPeerAware(byte[] peerMacAddress) {
+ if (peerMacAddress == null) {
+ throw new IllegalArgumentException("Null peerMacAddress");
+ }
+
+ this.peerMacAddress = peerMacAddress;
+ peerHandle = null;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (peerHandle == null) {
+ dest.writeBoolean(false);
+ dest.writeByteArray(peerMacAddress);
+ } else {
+ dest.writeBoolean(true);
+ dest.writeInt(peerHandle.peerId);
+ }
+ }
+
+ public static final Creator<RttPeerAware> CREATOR = new Creator<RttPeerAware>() {
+ @Override
+ public RttPeerAware[] newArray(int size) {
+ return new RttPeerAware[size];
+ }
+
+ @Override
+ public RttPeerAware createFromParcel(Parcel in) {
+ boolean peerHandleAvail = in.readBoolean();
+ if (peerHandleAvail) {
+ return new RttPeerAware(new PeerHandle(in.readInt()));
+ } else {
+ return new RttPeerAware(in.createByteArray());
+ }
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttPeerAware: peerHandle=").append(
+ peerHandle == null ? "<null>" : Integer.toString(peerHandle.peerId)).append(
+ ", peerMacAddress=").append(peerMacAddress == null ? "<null>"
+ : new String(HexEncoding.encode(peerMacAddress))).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RttPeerAware)) {
+ return false;
+ }
+
+ RttPeerAware lhs = (RttPeerAware) o;
+
+ return Objects.equals(peerHandle, lhs.peerHandle) && Arrays.equals(peerMacAddress,
+ lhs.peerMacAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(peerHandle.peerId, peerMacAddress);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
index 918803e..93e52ae 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResult.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -16,13 +16,16 @@
package android.net.wifi.rtt;
+import android.annotation.IntDef;
+import android.net.wifi.aware.PeerHandle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
import libcore.util.HexEncoding;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -40,28 +43,61 @@
public final class RangingResult implements Parcelable {
private static final String TAG = "RangingResult";
+ /** @hide */
+ @IntDef({STATUS_SUCCESS, STATUS_FAIL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangeResultStatus {
+ }
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
+ * successful and distance value is valid.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
+ * and the distance value is invalid.
+ */
+ public static final int STATUS_FAIL = 1;
+
private final int mStatus;
private final byte[] mMac;
- private final int mDistanceCm;
- private final int mDistanceStdDevCm;
+ private final PeerHandle mPeerHandle;
+ private final int mDistanceMm;
+ private final int mDistanceStdDevMm;
private final int mRssi;
private final long mTimestamp;
/** @hide */
- public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
- long timestamp) {
+ public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm,
+ int distanceStdDevMm, int rssi, long timestamp) {
mStatus = status;
mMac = mac;
- mDistanceCm = distanceCm;
- mDistanceStdDevCm = distanceStdDevCm;
+ mPeerHandle = null;
+ mDistanceMm = distanceMm;
+ mDistanceStdDevMm = distanceStdDevMm;
+ mRssi = rssi;
+ mTimestamp = timestamp;
+ }
+
+ /** @hide */
+ public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
+ int distanceStdDevMm, int rssi, long timestamp) {
+ mStatus = status;
+ mMac = null;
+ mPeerHandle = peerHandle;
+ mDistanceMm = distanceMm;
+ mDistanceStdDevMm = distanceStdDevMm;
mRssi = rssi;
mTimestamp = timestamp;
}
/**
- * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
- * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+ * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
+ * {@link #STATUS_FAIL} in case of failure.
*/
+ @RangeResultStatus
public int getStatus() {
return mStatus;
}
@@ -70,57 +106,80 @@
* @return The MAC address of the device whose range measurement was requested. Will correspond
* to the MAC address of the device in the {@link RangingRequest}.
* <p>
- * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+ * Will return a {@code null} for results corresponding to requests issued using a {@code
+ * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
*/
public byte[] getMacAddress() {
return mMac;
}
/**
- * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+ * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
+ * to the PeerHandle of the devices requested using
+ * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Will return a {@code null} for results corresponding to requests issued using a MAC address.
*/
- public int getDistanceCm() {
- if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
- Log.e(TAG, "getDistanceCm(): invalid value retrieved");
- }
- return mDistanceCm;
+ public PeerHandle getPeerHandle() {
+ return mPeerHandle;
}
/**
- * @return The standard deviation of the measured distance (in cm) to the device specified by
- * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
- * executed in a single RTT burst.
+ * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
+ * {@link #getPeerHandle()}.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
*/
- public int getDistanceStdDevCm() {
- if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
- Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+ public int getDistanceMm() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
}
- return mDistanceStdDevCm;
+ return mDistanceMm;
+ }
+
+ /**
+ * @return The standard deviation of the measured distance (in mm) to the device specified by
+ * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
+ * over the measurements executed in a single RTT burst.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getDistanceStdDevMm() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
+ return mDistanceStdDevMm;
}
/**
* @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
*/
public int getRssi() {
- if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
- // TODO: should this be an exception?
- Log.e(TAG, "getRssi(): invalid value retrieved");
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
}
return mRssi;
}
/**
- * @return The timestamp (in us) at which the ranging operation was performed
+ * @return The timestamp, in us since boot, at which the ranging operation was performed.
* <p>
- * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
*/
- public long getRangingTimestamp() {
+ public long getRangingTimestampUs() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getRangingTimestamp(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
return mTimestamp;
}
@@ -135,8 +194,14 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mStatus);
dest.writeByteArray(mMac);
- dest.writeInt(mDistanceCm);
- dest.writeInt(mDistanceStdDevCm);
+ if (mPeerHandle == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ dest.writeInt(mPeerHandle.peerId);
+ }
+ dest.writeInt(mDistanceMm);
+ dest.writeInt(mDistanceStdDevMm);
dest.writeInt(mRssi);
dest.writeLong(mTimestamp);
}
@@ -152,11 +217,22 @@
public RangingResult createFromParcel(Parcel in) {
int status = in.readInt();
byte[] mac = in.createByteArray();
- int distanceCm = in.readInt();
- int distanceStdDevCm = in.readInt();
+ boolean peerHandlePresent = in.readBoolean();
+ PeerHandle peerHandle = null;
+ if (peerHandlePresent) {
+ peerHandle = new PeerHandle(in.readInt());
+ }
+ int distanceMm = in.readInt();
+ int distanceStdDevMm = in.readInt();
int rssi = in.readInt();
long timestamp = in.readLong();
- return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+ if (peerHandlePresent) {
+ return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
+ timestamp);
+ } else {
+ return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
+ timestamp);
+ }
}
};
@@ -164,9 +240,10 @@
@Override
public String toString() {
return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
- mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
- ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
- mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+ mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append(
+ ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(
+ ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append(
+ mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append(
mTimestamp).append("]").toString();
}
@@ -182,13 +259,15 @@
RangingResult lhs = (RangingResult) o;
- return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
- && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
- && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+ return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals(
+ mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
+ && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
+ && mTimestamp == lhs.mTimestamp;
}
@Override
public int hashCode() {
- return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+ return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
+ mTimestamp);
}
-}
\ No newline at end of file
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
index d7270ad..7405e82 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -16,35 +16,43 @@
package android.net.wifi.rtt;
+import android.annotation.IntDef;
import android.os.Handler;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Base class for ranging result callbacks. Should be extended by applications and set when calling
- * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
- * result from a range request will be called in this object.
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. If the
+ * ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)} will be
+ * called with a failure code. If the ranging operation is performed for each of the requested
+ * peers then the {@link #onRangingResults(List)} will be called with the set of results (@link
+ * {@link RangingResult}, each of which has its own success/failure code
+ * {@link RangingResult#getStatus()}.
*
* @hide RTT_API
*/
public abstract class RangingResultCallback {
- /**
- * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
- * operation was successful and distance value is valid.
- */
- public static final int STATUS_SUCCESS = 0;
+ /** @hide */
+ @IntDef({STATUS_CODE_FAIL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangingOperationStatus {
+ }
/**
- * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
- * operation failed and the distance value is invalid.
+ * A failure code for the whole ranging request operation. Indicates a failure.
*/
- public static final int STATUS_FAIL = 1;
+ public static final int STATUS_CODE_FAIL = 1;
/**
* Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
* devices specified in the request was attempted.
+ *
+ * @param code A status code indicating the type of failure.
*/
- public abstract void onRangingFailure();
+ public abstract void onRangingFailure(@RangingOperationStatus int code);
/**
* Called when a ranging operation was executed. The list of results corresponds to devices
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
index a085de1..435bb37 100644
--- a/wifi/java/android/net/wifi/rtt/WifiRttManager.java
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -83,17 +83,18 @@
}
@Override
- public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
- if (VDBG) {
- Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
- + results);
- }
+ public void onRangingFailure(int status) throws RemoteException {
+ if (VDBG) Log.v(TAG, "RttCallbackProxy: onRangingFailure: status=" + status);
mHandler.post(() -> {
- if (status == RangingResultCallback.STATUS_SUCCESS) {
- mCallback.onRangingResults(results);
- } else {
- mCallback.onRangingFailure();
- }
+ mCallback.onRangingFailure(status);
+ });
+ }
+
+ @Override
+ public void onRangingResults(List<RangingResult> results) throws RemoteException {
+ if (VDBG) Log.v(TAG, "RttCallbackProxy: onRanginResults: results=" + results);
+ mHandler.post(() -> {
+ mCallback.onRangingResults(results);
});
}
}
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
index 23c75ce..33bd982 100644
--- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.net.wifi.ScanResult;
+import android.net.wifi.aware.PeerHandle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
@@ -78,7 +79,7 @@
public void testRangeSuccess() throws Exception {
RangingRequest request = new RangingRequest.Builder().build();
List<RangingResult> results = new ArrayList<>();
- results.add(new RangingResult(RangingResultCallback.STATUS_SUCCESS, null, 15, 5, 10, 666));
+ results.add(new RangingResult(RangingResult.STATUS_SUCCESS, (byte[]) null, 15, 5, 10, 666));
RangingResultCallback callbackMock = mock(RangingResultCallback.class);
ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
@@ -88,7 +89,7 @@
callbackCaptor.capture());
// service calls back with success
- callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_SUCCESS, results);
+ callbackCaptor.getValue().onRangingResults(results);
mMockLooper.dispatchAll();
verify(callbackMock).onRangingResults(results);
@@ -100,6 +101,8 @@
*/
@Test
public void testRangeFail() throws Exception {
+ int failureCode = RangingResultCallback.STATUS_CODE_FAIL;
+
RangingRequest request = new RangingRequest.Builder().build();
RangingResultCallback callbackMock = mock(RangingResultCallback.class);
ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
@@ -110,9 +113,9 @@
callbackCaptor.capture());
// service calls back with failure code
- callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+ callbackCaptor.getValue().onRangingFailure(failureCode);
mMockLooper.dispatchAll();
- verify(callbackMock).onRangingFailure();
+ verify(callbackMock).onRangingFailure(failureCode);
verifyNoMoreInteractions(mockRttService, callbackMock);
}
@@ -132,10 +135,14 @@
List<ScanResult> scanResults2and3 = new ArrayList<>(2);
scanResults2and3.add(scanResult2);
scanResults2and3.add(scanResult3);
+ final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+ PeerHandle peerHandle1 = new PeerHandle(12);
RangingRequest.Builder builder = new RangingRequest.Builder();
- builder.addAp(scanResult1);
- builder.addAps(scanResults2and3);
+ builder.addAccessPoint(scanResult1);
+ builder.addAccessPoints(scanResults2and3);
+ builder.addWifiAwarePeer(mac1);
+ builder.addWifiAwarePeer(peerHandle1);
RangingRequest request = builder.build();
Parcel parcelW = Parcel.obtain();
@@ -157,20 +164,23 @@
@Test
public void testRangingRequestAtLimit() {
ScanResult scanResult = new ScanResult();
+ scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
List<ScanResult> scanResultList = new ArrayList<>();
- for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
+ for (int i = 0; i < RangingRequest.getMaxPeers() - 3; ++i) {
scanResultList.add(scanResult);
}
+ final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
// create request
RangingRequest.Builder builder = new RangingRequest.Builder();
- builder.addAp(scanResult);
- builder.addAps(scanResultList);
- builder.addAp(scanResult);
+ builder.addAccessPoint(scanResult);
+ builder.addAccessPoints(scanResultList);
+ builder.addAccessPoint(scanResult);
+ builder.addWifiAwarePeer(mac1);
RangingRequest request = builder.build();
// verify request
- request.enforceValidity();
+ request.enforceValidity(true);
}
/**
@@ -180,19 +190,35 @@
public void testRangingRequestPastLimit() {
ScanResult scanResult = new ScanResult();
List<ScanResult> scanResultList = new ArrayList<>();
- for (int i = 0; i < RangingRequest.getMaxPeers() - 1; ++i) {
+ for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
scanResultList.add(scanResult);
}
+ final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
// create request
RangingRequest.Builder builder = new RangingRequest.Builder();
- builder.addAp(scanResult);
- builder.addAps(scanResultList);
- builder.addAp(scanResult);
+ builder.addAccessPoint(scanResult);
+ builder.addAccessPoints(scanResultList);
+ builder.addAccessPoint(scanResult);
+ builder.addWifiAwarePeer(mac1);
RangingRequest request = builder.build();
// verify request
- request.enforceValidity();
+ request.enforceValidity(true);
+ }
+
+ /**
+ * Validate that Aware requests are invalid on devices which do not support Aware
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRangingRequestWithAwareWithNoAwareSupport() {
+ // create request
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ builder.addWifiAwarePeer(new PeerHandle(10));
+ RangingRequest request = builder.build();
+
+ // verify request
+ request.enforceValidity(false);
}
/**
@@ -201,13 +227,15 @@
@Test
public void testRangingResultsParcel() {
// Note: not validating parcel code of ScanResult (assumed to work)
- int status = RangingResultCallback.STATUS_SUCCESS;
+ int status = RangingResult.STATUS_SUCCESS;
final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false);
+ PeerHandle peerHandle = new PeerHandle(10);
int distanceCm = 105;
int distanceStdDevCm = 10;
int rssi = 5;
long timestamp = System.currentTimeMillis();
+ // RangingResults constructed with a MAC address
RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
timestamp);
@@ -222,5 +250,21 @@
RangingResult rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
assertEquals(result, rereadResult);
+
+ // RangingResults constructed with a PeerHandle
+ result = new RangingResult(status, peerHandle, distanceCm, distanceStdDevCm, rssi,
+ timestamp);
+
+ parcelW = Parcel.obtain();
+ result.writeToParcel(parcelW, 0);
+ bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(result, rereadResult);
}
}